about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/README.md34
-rw-r--r--nixos/doc/manual/administration/imperative-containers.section.md2
-rw-r--r--nixos/doc/manual/administration/nixos-state.section.md28
-rw-r--r--nixos/doc/manual/administration/running.md1
-rw-r--r--nixos/doc/manual/administration/system-state.chapter.md17
-rw-r--r--nixos/doc/manual/administration/systemd-state.section.md52
-rw-r--r--nixos/doc/manual/administration/zfs-state.section.md16
-rw-r--r--nixos/doc/manual/configuration/adding-custom-packages.section.md4
-rw-r--r--nixos/doc/manual/configuration/customizing-packages.section.md8
-rw-r--r--nixos/doc/manual/configuration/declarative-packages.section.md2
-rw-r--r--nixos/doc/manual/configuration/gpu-accel.chapter.md14
-rw-r--r--nixos/doc/manual/configuration/linux-kernel.chapter.md66
-rw-r--r--nixos/doc/manual/configuration/luks-file-systems.section.md42
-rw-r--r--nixos/doc/manual/configuration/modularity.section.md4
-rw-r--r--nixos/doc/manual/configuration/profiles.chapter.md1
-rw-r--r--nixos/doc/manual/configuration/profiles/perlless.section.md11
-rw-r--r--nixos/doc/manual/configuration/subversion.chapter.md4
-rw-r--r--nixos/doc/manual/configuration/user-mgmt.chapter.md15
-rw-r--r--nixos/doc/manual/configuration/x-windows.chapter.md12
-rw-r--r--nixos/doc/manual/configuration/xfce.chapter.md2
-rw-r--r--nixos/doc/manual/development/activation-script.section.md2
-rw-r--r--nixos/doc/manual/development/etc-overlay.section.md36
-rw-r--r--nixos/doc/manual/development/non-switchable-systems.section.md21
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md8
-rw-r--r--nixos/doc/manual/development/option-types.section.md9
-rw-r--r--nixos/doc/manual/development/running-nixos-tests-interactively.section.md21
-rw-r--r--nixos/doc/manual/development/settings-options.section.md2
-rw-r--r--nixos/doc/manual/development/unit-handling.section.md39
-rw-r--r--nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md13
-rw-r--r--nixos/doc/manual/development/writing-documentation.chapter.md12
-rw-r--r--nixos/doc/manual/installation/building-images-via-systemd-repart.chapter.md (renamed from nixos/modules/image/repart.md)0
-rw-r--r--nixos/doc/manual/installation/changing-config.chapter.md15
-rw-r--r--nixos/doc/manual/installation/installation.md1
-rw-r--r--nixos/doc/manual/installation/installing-pxe.section.md2
-rw-r--r--nixos/doc/manual/installation/installing-usb.section.md2
-rw-r--r--nixos/doc/manual/installation/upgrading.chapter.md16
-rw-r--r--nixos/doc/manual/release-notes/release-notes.md1
-rw-r--r--nixos/doc/manual/release-notes/rl-1509.section.md4
-rw-r--r--nixos/doc/manual/release-notes/rl-1609.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2003.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2009.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2105.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2111.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2205.section.md3
-rw-r--r--nixos/doc/manual/release-notes/rl-2211.section.md4
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md10
-rw-r--r--nixos/doc/manual/release-notes/rl-2311.section.md1526
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md260
-rw-r--r--nixos/lib/eval-config.nix17
-rw-r--r--nixos/lib/make-btrfs-fs.nix67
-rw-r--r--nixos/lib/make-disk-image.nix9
-rw-r--r--nixos/lib/make-options-doc/default.nix2
-rw-r--r--nixos/lib/make-single-disk-zfs-image.nix5
-rw-r--r--nixos/lib/make-squashfs.nix14
-rw-r--r--nixos/lib/qemu-common.nix1
-rw-r--r--nixos/lib/systemd-lib.nix117
-rw-r--r--nixos/lib/systemd-network-units.nix9
-rw-r--r--nixos/lib/test-driver/default.nix29
-rw-r--r--nixos/lib/test-driver/extract-docstrings.py42
-rw-r--r--nixos/lib/test-driver/pyproject.toml44
-rw-r--r--nixos/lib/test-driver/setup.py14
-rw-r--r--nixos/lib/test-driver/shell.nix2
-rwxr-xr-xnixos/lib/test-driver/test_driver/__init__.py20
-rw-r--r--nixos/lib/test-driver/test_driver/driver.py33
-rw-r--r--nixos/lib/test-driver/test_driver/logger.py14
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py140
-rw-r--r--nixos/lib/test-driver/test_driver/polling_condition.py8
-rw-r--r--nixos/lib/test-driver/test_driver/qmp.py98
-rw-r--r--nixos/lib/test-driver/test_driver/vlan.py2
-rw-r--r--nixos/lib/testing-python.nix1
-rw-r--r--nixos/lib/testing/driver.nix20
-rw-r--r--nixos/lib/testing/nodes.nix15
-rw-r--r--nixos/lib/testing/run.nix36
-rw-r--r--nixos/lib/utils.nix11
-rw-r--r--nixos/maintainers/option-usages.nix6
-rw-r--r--nixos/maintainers/scripts/azure-new/examples/basic/system.nix1
-rwxr-xr-xnixos/maintainers/scripts/ec2/create-amis.sh54
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix6
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-container-image.nix10
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix6
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-virtual-machine-image.nix10
-rwxr-xr-xnixos/maintainers/scripts/oci/create-image.sh24
-rwxr-xr-xnixos/maintainers/scripts/oci/upload-image.sh100
-rw-r--r--nixos/maintainers/scripts/openstack/openstack-image-zfs.nix8
-rw-r--r--nixos/modules/config/console.nix7
-rw-r--r--nixos/modules/config/fanout.nix49
-rw-r--r--nixos/modules/config/fonts/packages.nix2
-rw-r--r--nixos/modules/config/gnu.nix43
-rw-r--r--nixos/modules/config/iproute2.nix16
-rw-r--r--nixos/modules/config/krb5/default.nix369
-rw-r--r--nixos/modules/config/ldap.nix101
-rw-r--r--nixos/modules/config/ldso.nix58
-rw-r--r--nixos/modules/config/mysql.nix57
-rw-r--r--nixos/modules/config/nix-channel.nix11
-rw-r--r--nixos/modules/config/nix.nix8
-rw-r--r--nixos/modules/config/no-x-libs.nix6
-rw-r--r--nixos/modules/config/pulseaudio.nix23
-rw-r--r--nixos/modules/config/qt.nix165
-rw-r--r--nixos/modules/config/shells-environment.nix3
-rw-r--r--nixos/modules/config/stevenblack.nix4
-rw-r--r--nixos/modules/config/stub-ld.nix56
-rw-r--r--nixos/modules/config/swap.nix3
-rw-r--r--nixos/modules/config/sysctl.nix33
-rw-r--r--nixos/modules/config/system-path.nix19
-rw-r--r--nixos/modules/config/terminfo.nix35
-rw-r--r--nixos/modules/config/update-users-groups.pl8
-rw-r--r--nixos/modules/config/users-groups.nix130
-rw-r--r--nixos/modules/config/xdg/portal.nix67
-rw-r--r--nixos/modules/hardware/all-firmware.nix31
-rw-r--r--nixos/modules/hardware/ckb-next.nix9
-rw-r--r--nixos/modules/hardware/corectrl.nix4
-rw-r--r--nixos/modules/hardware/cpu/amd-sev.nix89
-rw-r--r--nixos/modules/hardware/cpu/x86-msr.nix91
-rw-r--r--nixos/modules/hardware/decklink.nix16
-rw-r--r--nixos/modules/hardware/device-tree.nix66
-rw-r--r--nixos/modules/hardware/digitalbitbox.nix9
-rw-r--r--nixos/modules/hardware/glasgow.nix23
-rw-r--r--nixos/modules/hardware/i2c.nix2
-rw-r--r--nixos/modules/hardware/infiniband.nix58
-rw-r--r--nixos/modules/hardware/keyboard/qmk.nix1
-rw-r--r--nixos/modules/hardware/keyboard/uhk.nix2
-rw-r--r--nixos/modules/hardware/keyboard/zsa.nix2
-rw-r--r--nixos/modules/hardware/openrazer.nix2
-rw-r--r--nixos/modules/hardware/opentabletdriver.nix9
-rw-r--r--nixos/modules/hardware/tuxedo-keyboard.nix2
-rw-r--r--nixos/modules/hardware/usb-storage.nix2
-rw-r--r--nixos/modules/hardware/video/amdgpu-pro.nix10
-rw-r--r--nixos/modules/hardware/video/nvidia.nix610
-rw-r--r--nixos/modules/hardware/video/webcam/facetimehd.nix2
-rw-r--r--nixos/modules/hardware/video/webcam/ipu6.nix16
-rw-r--r--nixos/modules/i18n/input-method/fcitx5.nix13
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix2
-rw-r--r--nixos/modules/image/repart-image.nix110
-rw-r--r--nixos/modules/image/repart.nix186
-rw-r--r--nixos/modules/installer/cd-dvd/channel.nix54
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix1
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-minimal.nix4
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix132
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl32
-rw-r--r--nixos/modules/installer/tools/nixos-option/default.nix1
-rw-r--r--nixos/modules/installer/tools/tools.nix42
-rw-r--r--nixos/modules/installer/virtualbox-demo.nix2
-rw-r--r--nixos/modules/misc/documentation.nix8
-rw-r--r--nixos/modules/misc/ids.nix12
-rw-r--r--nixos/modules/misc/locate.nix43
-rw-r--r--nixos/modules/misc/mandoc.nix161
-rw-r--r--nixos/modules/misc/nixops-autoluks.nix2
-rw-r--r--nixos/modules/misc/nixpkgs.nix34
-rw-r--r--nixos/modules/misc/version.nix76
-rw-r--r--nixos/modules/module-list.nix108
-rw-r--r--nixos/modules/profiles/hardened.nix4
-rw-r--r--nixos/modules/profiles/image-based-appliance.nix26
-rw-r--r--nixos/modules/profiles/installation-device.nix4
-rw-r--r--nixos/modules/profiles/macos-builder.nix21
-rw-r--r--nixos/modules/profiles/minimal.nix11
-rw-r--r--nixos/modules/profiles/perlless.nix31
-rw-r--r--nixos/modules/programs/_1password-gui.nix2
-rw-r--r--nixos/modules/programs/_1password.nix2
-rw-r--r--nixos/modules/programs/atop.nix14
-rw-r--r--nixos/modules/programs/ausweisapp.nix6
-rw-r--r--nixos/modules/programs/bandwhich.nix2
-rw-r--r--nixos/modules/programs/bash/bash.nix2
-rw-r--r--nixos/modules/programs/browserpass.nix2
-rw-r--r--nixos/modules/programs/calls.nix2
-rw-r--r--nixos/modules/programs/captive-browser.nix10
-rw-r--r--nixos/modules/programs/cdemu.nix13
-rw-r--r--nixos/modules/programs/clash-verge.nix14
-rw-r--r--nixos/modules/programs/cnping.nix2
-rw-r--r--nixos/modules/programs/darling.nix2
-rw-r--r--nixos/modules/programs/digitalbitbox/default.nix9
-rw-r--r--nixos/modules/programs/direnv.nix31
-rw-r--r--nixos/modules/programs/dmrconfig.nix7
-rw-r--r--nixos/modules/programs/dublin-traceroute.nix31
-rw-r--r--nixos/modules/programs/ecryptfs.nix31
-rw-r--r--nixos/modules/programs/environment.nix11
-rw-r--r--nixos/modules/programs/evince.nix7
-rw-r--r--nixos/modules/programs/feedbackd.nix13
-rw-r--r--nixos/modules/programs/file-roller.nix7
-rw-r--r--nixos/modules/programs/firefox.nix113
-rw-r--r--nixos/modules/programs/firejail.nix2
-rw-r--r--nixos/modules/programs/fish.nix23
-rw-r--r--nixos/modules/programs/flashrom.nix2
-rw-r--r--nixos/modules/programs/flexoptix-app.nix7
-rw-r--r--nixos/modules/programs/gamemode.nix2
-rw-r--r--nixos/modules/programs/gamescope.nix9
-rw-r--r--nixos/modules/programs/git.nix24
-rw-r--r--nixos/modules/programs/gnupg.nix38
-rw-r--r--nixos/modules/programs/gpaste.nix2
-rw-r--r--nixos/modules/programs/htop.nix9
-rw-r--r--nixos/modules/programs/hyprland.nix20
-rw-r--r--nixos/modules/programs/i3lock.nix14
-rw-r--r--nixos/modules/programs/iay.nix4
-rw-r--r--nixos/modules/programs/java.nix9
-rw-r--r--nixos/modules/programs/joycond-cemuhook.nix17
-rw-r--r--nixos/modules/programs/k40-whisperer.nix10
-rw-r--r--nixos/modules/programs/kdeconnect.nix12
-rw-r--r--nixos/modules/programs/mininet.nix24
-rw-r--r--nixos/modules/programs/minipro.nix2
-rw-r--r--nixos/modules/programs/mosh.nix24
-rw-r--r--nixos/modules/programs/mtr.nix9
-rw-r--r--nixos/modules/programs/nano.nix33
-rw-r--r--nixos/modules/programs/neovim.nix7
-rw-r--r--nixos/modules/programs/nexttrace.nix2
-rw-r--r--nixos/modules/programs/nix-index.nix7
-rw-r--r--nixos/modules/programs/nix-ld.nix4
-rw-r--r--nixos/modules/programs/nncp.nix7
-rw-r--r--nixos/modules/programs/noisetorch.nix9
-rw-r--r--nixos/modules/programs/npm.nix10
-rw-r--r--nixos/modules/programs/oddjobd.nix7
-rw-r--r--nixos/modules/programs/openvpn3.nix20
-rw-r--r--nixos/modules/programs/partition-manager.nix2
-rw-r--r--nixos/modules/programs/projecteur.nix20
-rw-r--r--nixos/modules/programs/proxychains.nix4
-rw-r--r--nixos/modules/programs/qdmr.nix2
-rw-r--r--nixos/modules/programs/regreet.nix23
-rw-r--r--nixos/modules/programs/rust-motd.nix65
-rw-r--r--nixos/modules/programs/screen.nix38
-rw-r--r--nixos/modules/programs/shadow.nix2
-rw-r--r--nixos/modules/programs/singularity.nix39
-rw-r--r--nixos/modules/programs/skim.nix4
-rw-r--r--nixos/modules/programs/ssh.nix30
-rw-r--r--nixos/modules/programs/starship.nix57
-rw-r--r--nixos/modules/programs/streamdeck-ui.nix4
-rw-r--r--nixos/modules/programs/tsm-client.nix310
-rw-r--r--nixos/modules/programs/vim.nix10
-rw-r--r--nixos/modules/programs/virt-manager.nix16
-rw-r--r--nixos/modules/programs/wayland/cardboard.nix24
-rw-r--r--nixos/modules/programs/wayland/labwc.nix25
-rw-r--r--nixos/modules/programs/wayland/river.nix12
-rw-r--r--nixos/modules/programs/wayland/sway.nix45
-rw-r--r--nixos/modules/programs/wayland/waybar.nix2
-rw-r--r--nixos/modules/programs/wayland/wayfire.nix6
-rw-r--r--nixos/modules/programs/weylus.nix7
-rw-r--r--nixos/modules/programs/winbox.nix24
-rw-r--r--nixos/modules/programs/wireshark.nix9
-rw-r--r--nixos/modules/programs/xonsh.nix10
-rw-r--r--nixos/modules/programs/yazi.nix53
-rw-r--r--nixos/modules/programs/yubikey-touch-detector.nix21
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.md2
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.nix10
-rw-r--r--nixos/modules/programs/zsh/zsh-autoenv.nix10
-rw-r--r--nixos/modules/rename.nix5
-rw-r--r--nixos/modules/security/acme/default.md30
-rw-r--r--nixos/modules/security/acme/default.nix202
-rw-r--r--nixos/modules/security/apparmor.nix3
-rw-r--r--nixos/modules/security/apparmor/profiles.nix6
-rw-r--r--nixos/modules/security/auditd.nix4
-rw-r--r--nixos/modules/security/duosec.nix17
-rw-r--r--nixos/modules/security/google_oslogin.nix4
-rw-r--r--nixos/modules/security/ipa.nix50
-rw-r--r--nixos/modules/security/krb5/default.nix90
-rw-r--r--nixos/modules/security/krb5/krb5-conf-format.nix88
-rw-r--r--nixos/modules/security/lock-kernel-modules.nix4
-rw-r--r--nixos/modules/security/pam.nix819
-rw-r--r--nixos/modules/security/pam_mount.nix6
-rw-r--r--nixos/modules/security/please.nix9
-rw-r--r--nixos/modules/security/polkit.nix3
-rw-r--r--nixos/modules/security/sudo-rs.nix269
-rw-r--r--nixos/modules/security/sudo.nix149
-rw-r--r--nixos/modules/security/wrappers/default.nix102
-rw-r--r--nixos/modules/security/wrappers/wrapper.c162
-rw-r--r--nixos/modules/security/wrappers/wrapper.nix11
-rw-r--r--nixos/modules/services/admin/meshcentral.nix7
-rw-r--r--nixos/modules/services/admin/pgadmin.nix26
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix9
-rw-r--r--nixos/modules/services/audio/botamusique.nix7
-rw-r--r--nixos/modules/services/audio/castopod.md22
-rw-r--r--nixos/modules/services/audio/castopod.nix287
-rw-r--r--nixos/modules/services/audio/gmediarender.nix3
-rw-r--r--nixos/modules/services/audio/gonic.nix1
-rw-r--r--nixos/modules/services/audio/goxlr-utility.nix2
-rw-r--r--nixos/modules/services/audio/jack.nix13
-rw-r--r--nixos/modules/services/audio/jmusicbot.nix8
-rw-r--r--nixos/modules/services/audio/mopidy.nix2
-rw-r--r--nixos/modules/services/audio/mympd.nix129
-rw-r--r--nixos/modules/services/audio/navidrome.nix9
-rw-r--r--nixos/modules/services/audio/slimserver.nix9
-rw-r--r--nixos/modules/services/audio/spotifyd.nix1
-rw-r--r--nixos/modules/services/audio/tts.nix1
-rw-r--r--nixos/modules/services/audio/wyoming/faster-whisper.nix13
-rw-r--r--nixos/modules/services/audio/wyoming/openwakeword.nix163
-rw-r--r--nixos/modules/services/audio/wyoming/piper.nix7
-rw-r--r--nixos/modules/services/audio/ympd.nix1
-rw-r--r--nixos/modules/services/backup/bacula.nix40
-rw-r--r--nixos/modules/services/backup/borgbackup.nix67
-rw-r--r--nixos/modules/services/backup/borgmatic.nix57
-rw-r--r--nixos/modules/services/backup/btrbk.nix91
-rw-r--r--nixos/modules/services/backup/duplicati.nix7
-rw-r--r--nixos/modules/services/backup/postgresql-backup.nix5
-rw-r--r--nixos/modules/services/backup/postgresql-wal-receiver.nix10
-rw-r--r--nixos/modules/services/backup/restic-rest-server.nix7
-rw-r--r--nixos/modules/services/backup/restic.nix92
-rw-r--r--nixos/modules/services/backup/sanoid.nix2
-rw-r--r--nixos/modules/services/backup/snapraid.nix (renamed from nixos/modules/tasks/snapraid.nix)15
-rw-r--r--nixos/modules/services/backup/syncoid.nix12
-rw-r--r--nixos/modules/services/backup/tsm.nix11
-rw-r--r--nixos/modules/services/backup/znapzend.nix20
-rw-r--r--nixos/modules/services/backup/zrepl.nix7
-rw-r--r--nixos/modules/services/blockchain/ethereum/erigon.nix4
-rw-r--r--nixos/modules/services/blockchain/ethereum/geth.nix7
-rw-r--r--nixos/modules/services/cluster/corosync/default.nix7
-rw-r--r--nixos/modules/services/cluster/hadoop/default.nix25
-rw-r--r--nixos/modules/services/cluster/hadoop/hbase.nix7
-rw-r--r--nixos/modules/services/cluster/hadoop/yarn.nix2
-rw-r--r--nixos/modules/services/cluster/k3s/default.nix7
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix7
-rw-r--r--nixos/modules/services/cluster/kubernetes/flannel.nix9
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix2
-rw-r--r--nixos/modules/services/cluster/pacemaker/default.nix7
-rw-r--r--nixos/modules/services/cluster/spark/default.nix40
-rw-r--r--nixos/modules/services/computing/boinc/client.nix12
-rw-r--r--nixos/modules/services/computing/foldingathome/client.nix13
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix12
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix38
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/worker.nix10
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agents.nix8
-rw-r--r--nixos/modules/services/continuous-integration/gitea-actions-runner.nix5
-rw-r--r--nixos/modules/services/continuous-integration/github-runner/options.nix10
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix8
-rw-r--r--nixos/modules/services/continuous-integration/hail.nix61
-rw-r--r--nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix11
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix8
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/default.nix8
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/slave.nix9
-rw-r--r--nixos/modules/services/continuous-integration/woodpecker/agents.nix61
-rw-r--r--nixos/modules/services/continuous-integration/woodpecker/server.nix10
-rw-r--r--nixos/modules/services/databases/aerospike.nix20
-rw-r--r--nixos/modules/services/databases/cassandra.nix13
-rw-r--r--nixos/modules/services/databases/clickhouse.nix9
-rw-r--r--nixos/modules/services/databases/cockroachdb.nix9
-rw-r--r--nixos/modules/services/databases/couchdb.nix11
-rw-r--r--nixos/modules/services/databases/dgraph.nix2
-rw-r--r--nixos/modules/services/databases/ferretdb.nix79
-rw-r--r--nixos/modules/services/databases/firebird.nix14
-rw-r--r--nixos/modules/services/databases/hbase-standalone.nix10
-rw-r--r--nixos/modules/services/databases/influxdb.nix8
-rw-r--r--nixos/modules/services/databases/influxdb2.nix8
-rw-r--r--nixos/modules/services/databases/lldap.nix3
-rw-r--r--nixos/modules/services/databases/monetdb.nix7
-rw-r--r--nixos/modules/services/databases/mongodb.nix9
-rw-r--r--nixos/modules/services/databases/neo4j.nix9
-rw-r--r--nixos/modules/services/databases/openldap.nix10
-rw-r--r--nixos/modules/services/databases/opentsdb.nix9
-rw-r--r--nixos/modules/services/databases/pgbouncer.nix9
-rw-r--r--nixos/modules/services/databases/pgmanage.nix13
-rw-r--r--nixos/modules/services/databases/postgresql.md153
-rw-r--r--nixos/modules/services/databases/postgresql.nix110
-rw-r--r--nixos/modules/services/databases/redis.nix15
-rw-r--r--nixos/modules/services/databases/surrealdb.nix40
-rw-r--r--nixos/modules/services/databases/victoriametrics.nix9
-rw-r--r--nixos/modules/services/desktops/ayatana-indicators.nix58
-rw-r--r--nixos/modules/services/desktops/deepin/app-services.nix13
-rw-r--r--nixos/modules/services/desktops/deepin/dde-api.nix4
-rw-r--r--nixos/modules/services/desktops/deepin/dde-daemon.nix2
-rw-r--r--nixos/modules/services/desktops/flatpak.md1
-rw-r--r--nixos/modules/services/desktops/flatpak.nix1
-rw-r--r--nixos/modules/services/desktops/geoclue2.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/at-spi2-core.nix2
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-browser-connector.nix6
-rw-r--r--nixos/modules/services/desktops/gnome/gnome-initial-setup.nix3
-rw-r--r--nixos/modules/services/desktops/gvfs.nix7
-rw-r--r--nixos/modules/services/desktops/pipewire/pipewire.nix200
-rw-r--r--nixos/modules/services/desktops/seatd.nix51
-rw-r--r--nixos/modules/services/development/athens.md52
-rw-r--r--nixos/modules/services/development/athens.nix936
-rw-r--r--nixos/modules/services/development/distccd.nix9
-rw-r--r--nixos/modules/services/development/jupyter/default.nix15
-rw-r--r--nixos/modules/services/development/livebook.md50
-rw-r--r--nixos/modules/services/development/livebook.nix101
-rw-r--r--nixos/modules/services/development/nixseparatedebuginfod.nix105
-rw-r--r--nixos/modules/services/development/rstudio-server/default.nix10
-rw-r--r--nixos/modules/services/development/zammad.nix66
-rw-r--r--nixos/modules/services/display-managers/greetd.nix20
-rw-r--r--nixos/modules/services/editors/emacs.md15
-rw-r--r--nixos/modules/services/editors/emacs.nix30
-rw-r--r--nixos/modules/services/editors/infinoted.nix9
-rw-r--r--nixos/modules/services/finance/odoo.nix9
-rw-r--r--nixos/modules/services/games/archisteamfarm.nix (renamed from nixos/modules/services/games/asf.nix)135
-rw-r--r--nixos/modules/services/games/crossfire-server.nix12
-rw-r--r--nixos/modules/services/games/deliantra-server.nix12
-rw-r--r--nixos/modules/services/games/factorio.nix56
-rw-r--r--nixos/modules/services/games/mchprs.nix7
-rw-r--r--nixos/modules/services/games/minecraft-server.nix8
-rw-r--r--nixos/modules/services/games/openarena.nix2
-rw-r--r--nixos/modules/services/games/quake3-server.nix2
-rw-r--r--nixos/modules/services/games/teeworlds.nix2
-rw-r--r--nixos/modules/services/games/xonotic.nix198
-rw-r--r--nixos/modules/services/hardware/acpid.nix1
-rw-r--r--nixos/modules/services/hardware/auto-cpufreq.nix10
-rw-r--r--nixos/modules/services/hardware/auto-epp.nix80
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix11
-rw-r--r--nixos/modules/services/hardware/freefall.nix9
-rw-r--r--nixos/modules/services/hardware/fwupd.nix29
-rw-r--r--nixos/modules/services/hardware/iptsd.nix53
-rw-r--r--nixos/modules/services/hardware/joycond.nix9
-rw-r--r--nixos/modules/services/hardware/kanata.nix37
-rw-r--r--nixos/modules/services/hardware/keyd.nix38
-rw-r--r--nixos/modules/services/hardware/openrgb.nix16
-rw-r--r--nixos/modules/services/hardware/pcscd.nix9
-rw-r--r--nixos/modules/services/hardware/power-profiles-daemon.nix19
-rw-r--r--nixos/modules/services/hardware/sane.nix20
-rw-r--r--nixos/modules/services/hardware/supergfxd.nix2
-rw-r--r--nixos/modules/services/hardware/thermald.nix14
-rw-r--r--nixos/modules/services/hardware/thinkfan.nix2
-rw-r--r--nixos/modules/services/hardware/throttled.nix6
-rw-r--r--nixos/modules/services/hardware/tlp.nix4
-rw-r--r--nixos/modules/services/hardware/tuxedo-rs.nix49
-rw-r--r--nixos/modules/services/hardware/udev.nix10
-rw-r--r--nixos/modules/services/hardware/undervolt.nix11
-rw-r--r--nixos/modules/services/hardware/upower.nix9
-rw-r--r--nixos/modules/services/hardware/usbmuxd.nix2
-rw-r--r--nixos/modules/services/hardware/vdr.nix105
-rw-r--r--nixos/modules/services/home-automation/esphome.nix15
-rw-r--r--nixos/modules/services/home-automation/evcc.nix1
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix100
-rw-r--r--nixos/modules/services/home-automation/homeassistant-satellite.nix225
-rw-r--r--nixos/modules/services/home-automation/zigbee2mqtt.nix12
-rw-r--r--nixos/modules/services/home-automation/zwave-js.nix152
-rw-r--r--nixos/modules/services/logging/SystemdJournal2Gelf.nix9
-rw-r--r--nixos/modules/services/logging/filebeat.nix11
-rw-r--r--nixos/modules/services/logging/fluentd.nix7
-rw-r--r--nixos/modules/services/logging/heartbeat.nix10
-rw-r--r--nixos/modules/services/logging/journalbeat.nix9
-rw-r--r--nixos/modules/services/logging/journaldriver.nix1
-rw-r--r--nixos/modules/services/logging/logcheck.nix14
-rw-r--r--nixos/modules/services/logging/logrotate.nix12
-rw-r--r--nixos/modules/services/logging/logstash.nix9
-rw-r--r--nixos/modules/services/logging/syslog-ng.nix11
-rw-r--r--nixos/modules/services/logging/ulogd.nix25
-rw-r--r--nixos/modules/services/logging/vector.nix8
-rw-r--r--nixos/modules/services/mail/dovecot.nix188
-rw-r--r--nixos/modules/services/mail/exim.nix10
-rw-r--r--nixos/modules/services/mail/listmonk.nix13
-rw-r--r--nixos/modules/services/mail/mailman.nix13
-rw-r--r--nixos/modules/services/mail/mlmmj.nix17
-rw-r--r--nixos/modules/services/mail/nullmailer.nix8
-rw-r--r--nixos/modules/services/mail/offlineimap.nix7
-rw-r--r--nixos/modules/services/mail/opensmtpd.nix7
-rw-r--r--nixos/modules/services/mail/postfix.nix15
-rw-r--r--nixos/modules/services/mail/public-inbox.nix7
-rw-r--r--nixos/modules/services/mail/roundcube.nix75
-rw-r--r--nixos/modules/services/mail/rspamd-trainer.nix76
-rw-r--r--nixos/modules/services/mail/stalwart-mail.nix4
-rw-r--r--nixos/modules/services/mail/sympa.nix12
-rw-r--r--nixos/modules/services/matrix/appservice-discord.nix13
-rw-r--r--nixos/modules/services/matrix/appservice-irc.nix2
-rw-r--r--nixos/modules/services/matrix/conduit.nix9
-rw-r--r--nixos/modules/services/matrix/matrix-sliding-sync.nix24
-rw-r--r--nixos/modules/services/matrix/maubot.md103
-rw-r--r--nixos/modules/services/matrix/maubot.nix459
-rw-r--r--nixos/modules/services/matrix/mautrix-facebook.nix6
-rw-r--r--nixos/modules/services/matrix/mautrix-telegram.nix5
-rw-r--r--nixos/modules/services/matrix/mautrix-whatsapp.nix4
-rw-r--r--nixos/modules/services/matrix/mjolnir.nix4
-rw-r--r--nixos/modules/services/matrix/mx-puppet-discord.nix4
-rw-r--r--nixos/modules/services/matrix/synapse-log_config.yaml25
-rw-r--r--nixos/modules/services/matrix/synapse.md10
-rw-r--r--nixos/modules/services/matrix/synapse.nix748
-rw-r--r--nixos/modules/services/misc/airsonic.nix11
-rw-r--r--nixos/modules/services/misc/amazon-ssm-agent.nix (renamed from nixos/modules/services/misc/ssm-agent.nix)48
-rw-r--r--nixos/modules/services/misc/ananicy.nix18
-rw-r--r--nixos/modules/services/misc/anki-sync-server.md68
-rw-r--r--nixos/modules/services/misc/anki-sync-server.nix140
-rw-r--r--nixos/modules/services/misc/ankisyncd.nix13
-rw-r--r--nixos/modules/services/misc/apache-kafka.nix197
-rw-r--r--nixos/modules/services/misc/atuin.nix6
-rw-r--r--nixos/modules/services/misc/autofs.nix2
-rw-r--r--nixos/modules/services/misc/autosuspend.nix4
-rw-r--r--nixos/modules/services/misc/bcg.nix9
-rw-r--r--nixos/modules/services/misc/calibre-server.nix2
-rw-r--r--nixos/modules/services/misc/cfdyndns.nix31
-rw-r--r--nixos/modules/services/misc/cgminer.nix7
-rw-r--r--nixos/modules/services/misc/clipcat.nix7
-rw-r--r--nixos/modules/services/misc/clipmenu.nix7
-rw-r--r--[-rwxr-xr-x]nixos/modules/services/misc/confd.nix7
-rw-r--r--nixos/modules/services/misc/disnix.nix7
-rw-r--r--nixos/modules/services/misc/docker-registry.nix8
-rw-r--r--nixos/modules/services/misc/domoticz.nix1
-rw-r--r--nixos/modules/services/misc/dwm-status.nix10
-rw-r--r--nixos/modules/services/misc/dysnomia.nix2
-rw-r--r--nixos/modules/services/misc/etcd.nix2
-rw-r--r--nixos/modules/services/misc/etesync-dav.nix1
-rw-r--r--nixos/modules/services/misc/forgejo.md79
-rw-r--r--nixos/modules/services/misc/forgejo.nix679
-rw-r--r--nixos/modules/services/misc/freeswitch.nix9
-rw-r--r--nixos/modules/services/misc/gitea.nix47
-rw-r--r--nixos/modules/services/misc/gitlab.nix43
-rw-r--r--nixos/modules/services/misc/gollum.nix11
-rw-r--r--nixos/modules/services/misc/gpsd.nix12
-rw-r--r--nixos/modules/services/misc/greenclip.nix7
-rw-r--r--nixos/modules/services/misc/guix/default.nix406
-rw-r--r--nixos/modules/services/misc/heisenbridge.nix9
-rw-r--r--nixos/modules/services/misc/homepage-dashboard.nix2
-rw-r--r--nixos/modules/services/misc/input-remapper.nix2
-rw-r--r--nixos/modules/services/misc/jackett.nix7
-rw-r--r--nixos/modules/services/misc/jellyfin.nix12
-rw-r--r--nixos/modules/services/misc/kafka.md63
-rw-r--r--nixos/modules/services/misc/klipper.nix11
-rw-r--r--nixos/modules/services/misc/libreddit.nix7
-rw-r--r--nixos/modules/services/misc/lidarr.nix7
-rw-r--r--nixos/modules/services/misc/llama-cpp.nix111
-rw-r--r--nixos/modules/services/misc/mbpfan.nix26
-rw-r--r--nixos/modules/services/misc/mediatomb.nix12
-rw-r--r--nixos/modules/services/misc/metabase.nix1
-rw-r--r--nixos/modules/services/misc/moonraker.nix15
-rw-r--r--nixos/modules/services/misc/nitter.nix30
-rw-r--r--nixos/modules/services/misc/nix-gc.nix25
-rw-r--r--nixos/modules/services/misc/nix-ssh-serve.nix4
-rw-r--r--nixos/modules/services/misc/ntfy-sh.nix13
-rw-r--r--nixos/modules/services/misc/nzbhydra2.nix7
-rw-r--r--nixos/modules/services/misc/ollama.nix50
-rw-r--r--nixos/modules/services/misc/packagekit.nix4
-rw-r--r--nixos/modules/services/misc/paperless.nix108
-rw-r--r--nixos/modules/services/misc/plex.nix11
-rw-r--r--nixos/modules/services/misc/polaris.nix2
-rw-r--r--nixos/modules/services/misc/portunus.nix17
-rw-r--r--nixos/modules/services/misc/preload.nix31
-rw-r--r--nixos/modules/services/misc/prowlarr.nix2
-rw-r--r--nixos/modules/services/misc/pufferpanel.nix2
-rw-r--r--nixos/modules/services/misc/radarr.nix8
-rw-r--r--nixos/modules/services/misc/readarr.nix7
-rw-r--r--nixos/modules/services/misc/redmine.nix16
-rw-r--r--nixos/modules/services/misc/rippled.nix7
-rw-r--r--nixos/modules/services/misc/rkvm.nix164
-rw-r--r--nixos/modules/services/misc/rmfakecloud.nix11
-rw-r--r--nixos/modules/services/misc/rshim.nix4
-rw-r--r--nixos/modules/services/misc/sickbeard.nix9
-rw-r--r--nixos/modules/services/misc/soft-serve.nix99
-rw-r--r--nixos/modules/services/misc/sonarr.nix9
-rw-r--r--nixos/modules/services/misc/sourcehut/default.nix197
-rw-r--r--nixos/modules/services/misc/sourcehut/service.nix589
-rw-r--r--nixos/modules/services/misc/spice-autorandr.nix26
-rw-r--r--nixos/modules/services/misc/spice-webdavd.nix7
-rw-r--r--nixos/modules/services/misc/tandoor-recipes.nix9
-rw-r--r--nixos/modules/services/misc/taskserver/helper-tool.py34
-rw-r--r--nixos/modules/services/misc/tautulli.nix9
-rw-r--r--nixos/modules/services/misc/tp-auto-kbbl.nix9
-rw-r--r--nixos/modules/services/misc/tuxclocker.nix71
-rw-r--r--nixos/modules/services/misc/xmr-stak.nix8
-rw-r--r--nixos/modules/services/misc/xmrig.nix14
-rw-r--r--nixos/modules/services/misc/zoneminder.nix4
-rw-r--r--nixos/modules/services/misc/zookeeper.nix7
-rw-r--r--nixos/modules/services/monitoring/arbtt.nix9
-rw-r--r--nixos/modules/services/monitoring/below.nix2
-rw-r--r--nixos/modules/services/monitoring/bosun.nix11
-rw-r--r--nixos/modules/services/monitoring/certspotter.md74
-rw-r--r--nixos/modules/services/monitoring/certspotter.nix143
-rw-r--r--nixos/modules/services/monitoring/cockpit.nix4
-rw-r--r--nixos/modules/services/monitoring/collectd.nix9
-rw-r--r--nixos/modules/services/monitoring/datadog-agent.nix15
-rw-r--r--nixos/modules/services/monitoring/goss.md44
-rw-r--r--nixos/modules/services/monitoring/goss.nix86
-rw-r--r--nixos/modules/services/monitoring/grafana-agent.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana-image-renderer.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana.nix40
-rw-r--r--nixos/modules/services/monitoring/graphite.nix2
-rw-r--r--nixos/modules/services/monitoring/heapster.nix7
-rw-r--r--nixos/modules/services/monitoring/karma.nix9
-rw-r--r--nixos/modules/services/monitoring/kthxbye.nix9
-rw-r--r--nixos/modules/services/monitoring/librenms.nix624
-rw-r--r--nixos/modules/services/monitoring/loki.nix2
-rw-r--r--nixos/modules/services/monitoring/mackerel-agent.nix7
-rw-r--r--nixos/modules/services/monitoring/metricbeat.nix11
-rw-r--r--nixos/modules/services/monitoring/mimir.nix21
-rw-r--r--nixos/modules/services/monitoring/munin.nix51
-rw-r--r--nixos/modules/services/monitoring/nagios.nix2
-rw-r--r--nixos/modules/services/monitoring/netdata.nix30
-rw-r--r--nixos/modules/services/monitoring/ocsinventory-agent.md33
-rw-r--r--nixos/modules/services/monitoring/ocsinventory-agent.nix134
-rw-r--r--nixos/modules/services/monitoring/opentelemetry-collector.nix9
-rw-r--r--nixos/modules/services/monitoring/parsedmarc.nix1
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix7
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix12
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix38
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix102
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix55
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/imap-mailstat.nix71
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/kea.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/knot.nix19
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/mongodb.nix68
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix15
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginx.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix145
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/php-fpm.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/ping.nix48
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pve.nix10
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix57
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix47
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/pushgateway.nix9
-rw-r--r--nixos/modules/services/monitoring/scollector.nix9
-rw-r--r--nixos/modules/services/monitoring/smartd.nix2
-rw-r--r--nixos/modules/services/monitoring/snmpd.nix83
-rw-r--r--nixos/modules/services/monitoring/teamviewer.nix1
-rw-r--r--nixos/modules/services/monitoring/telegraf.nix13
-rw-r--r--nixos/modules/services/monitoring/thanos.nix144
-rw-r--r--nixos/modules/services/monitoring/ups.nix430
-rw-r--r--nixos/modules/services/monitoring/uptime-kuma.nix7
-rw-r--r--nixos/modules/services/monitoring/vmagent.nix9
-rw-r--r--nixos/modules/services/monitoring/vmalert.nix9
-rw-r--r--nixos/modules/services/monitoring/watchdogd.nix131
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix9
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix4
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix4
-rw-r--r--nixos/modules/services/network-filesystems/ceph.nix10
-rw-r--r--nixos/modules/services/network-filesystems/drbd.nix4
-rw-r--r--nixos/modules/services/network-filesystems/eris-server.nix56
-rw-r--r--nixos/modules/services/network-filesystems/kubo.nix69
-rw-r--r--nixos/modules/services/network-filesystems/litestream/default.nix7
-rw-r--r--nixos/modules/services/network-filesystems/openafs/client.nix1
-rw-r--r--nixos/modules/services/network-filesystems/openafs/server.nix13
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/server.nix2
-rw-r--r--nixos/modules/services/network-filesystems/samba.nix14
-rw-r--r--nixos/modules/services/network-filesystems/tahoe.nix18
-rw-r--r--nixos/modules/services/network-filesystems/xtreemfs.nix10
-rw-r--r--nixos/modules/services/networking/acme-dns.nix4
-rw-r--r--nixos/modules/services/networking/adguardhome.nix5
-rw-r--r--nixos/modules/services/networking/alice-lg.nix2
-rw-r--r--nixos/modules/services/networking/asterisk.nix9
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix49
-rw-r--r--nixos/modules/services/networking/bee.nix8
-rw-r--r--nixos/modules/services/networking/bind.nix7
-rw-r--r--nixos/modules/services/networking/bird-lg.nix7
-rw-r--r--nixos/modules/services/networking/bird.nix9
-rw-r--r--nixos/modules/services/networking/birdwatcher.nix7
-rw-r--r--nixos/modules/services/networking/bitcoind.nix11
-rw-r--r--nixos/modules/services/networking/blockbook-frontend.nix7
-rw-r--r--nixos/modules/services/networking/centrifugo.nix123
-rw-r--r--nixos/modules/services/networking/cgit.nix2
-rw-r--r--nixos/modules/services/networking/cloudflared.nix7
-rw-r--r--nixos/modules/services/networking/connman.nix69
-rw-r--r--nixos/modules/services/networking/consul.nix17
-rw-r--r--nixos/modules/services/networking/coredns.nix14
-rw-r--r--nixos/modules/services/networking/corerad.nix7
-rw-r--r--nixos/modules/services/networking/create_ap.nix2
-rw-r--r--nixos/modules/services/networking/dae.nix181
-rw-r--r--nixos/modules/services/networking/dante.nix1
-rw-r--r--nixos/modules/services/networking/ddclient.nix234
-rw-r--r--nixos/modules/services/networking/deconz.nix125
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix2
-rw-r--r--nixos/modules/services/networking/dnsmasq.md68
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix6
-rw-r--r--nixos/modules/services/networking/ejabberd.nix13
-rw-r--r--nixos/modules/services/networking/envoy.nix2
-rw-r--r--nixos/modules/services/networking/epmd.nix10
-rw-r--r--nixos/modules/services/networking/ergo.nix1
-rw-r--r--nixos/modules/services/networking/expressvpn.nix1
-rw-r--r--nixos/modules/services/networking/fastnetmon-advanced.nix222
-rw-r--r--nixos/modules/services/networking/ferm.nix7
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.md2
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.nix8
-rw-r--r--nixos/modules/services/networking/firewall-iptables.nix4
-rw-r--r--nixos/modules/services/networking/flannel.nix7
-rw-r--r--nixos/modules/services/networking/frp.nix89
-rw-r--r--nixos/modules/services/networking/frr.nix1
-rw-r--r--nixos/modules/services/networking/ghostunnel.nix8
-rw-r--r--nixos/modules/services/networking/gns3-server.md31
-rw-r--r--nixos/modules/services/networking/gns3-server.nix263
-rw-r--r--nixos/modules/services/networking/gnunet.nix8
-rw-r--r--nixos/modules/services/networking/go-neb.nix2
-rw-r--r--nixos/modules/services/networking/gvpe.nix2
-rw-r--r--nixos/modules/services/networking/haproxy.nix2
-rw-r--r--nixos/modules/services/networking/harmonia.nix11
-rw-r--r--nixos/modules/services/networking/headscale.nix10
-rw-r--r--nixos/modules/services/networking/hostapd.nix34
-rw-r--r--nixos/modules/services/networking/hylafax/options.nix8
-rw-r--r--nixos/modules/services/networking/i2pd.nix19
-rw-r--r--nixos/modules/services/networking/icecream/daemon.nix7
-rw-r--r--nixos/modules/services/networking/icecream/scheduler.nix7
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/builder.sh2
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/default.nix3
-rw-r--r--nixos/modules/services/networking/iscsi/initiator.nix45
-rw-r--r--nixos/modules/services/networking/ivpn.nix2
-rw-r--r--nixos/modules/services/networking/iwd.nix13
-rw-r--r--nixos/modules/services/networking/jigasi.nix237
-rw-r--r--nixos/modules/services/networking/jool.nix313
-rw-r--r--nixos/modules/services/networking/kea.nix8
-rw-r--r--nixos/modules/services/networking/keepalived/default.nix30
-rw-r--r--nixos/modules/services/networking/knot.nix139
-rw-r--r--nixos/modules/services/networking/kresd.nix10
-rw-r--r--nixos/modules/services/networking/lambdabot.nix7
-rw-r--r--nixos/modules/services/networking/legit.nix4
-rw-r--r--nixos/modules/services/networking/lokinet.nix7
-rw-r--r--nixos/modules/services/networking/miniupnpd.nix53
-rw-r--r--nixos/modules/services/networking/miredo.nix9
-rw-r--r--nixos/modules/services/networking/morty.nix7
-rw-r--r--nixos/modules/services/networking/mosquitto.nix10
-rw-r--r--nixos/modules/services/networking/mtr-exporter.nix101
-rw-r--r--nixos/modules/services/networking/mullvad-vpn.nix18
-rw-r--r--nixos/modules/services/networking/multipath.nix10
-rw-r--r--nixos/modules/services/networking/murmur.nix7
-rw-r--r--nixos/modules/services/networking/mxisd.nix7
-rw-r--r--nixos/modules/services/networking/nar-serve.nix4
-rw-r--r--nixos/modules/services/networking/nat-nftables.nix31
-rw-r--r--nixos/modules/services/networking/nbd.nix1
-rw-r--r--nixos/modules/services/networking/nebula.nix9
-rw-r--r--nixos/modules/services/networking/netbird.nix7
-rw-r--r--nixos/modules/services/networking/netclient.nix27
-rw-r--r--nixos/modules/services/networking/networkmanager.nix233
-rw-r--r--nixos/modules/services/networking/nftables.nix25
-rw-r--r--nixos/modules/services/networking/ngircd.nix9
-rw-r--r--nixos/modules/services/networking/nix-serve.nix13
-rw-r--r--nixos/modules/services/networking/nomad.nix9
-rw-r--r--nixos/modules/services/networking/ntp/chrony.nix75
-rw-r--r--nixos/modules/services/networking/ntp/ntpd-rs.nix89
-rw-r--r--nixos/modules/services/networking/ntp/ntpd.nix2
-rw-r--r--nixos/modules/services/networking/ocserv.nix1
-rw-r--r--nixos/modules/services/networking/openconnect.nix2
-rw-r--r--nixos/modules/services/networking/peroxide.nix2
-rw-r--r--nixos/modules/services/networking/pixiecore.nix6
-rw-r--r--nixos/modules/services/networking/pleroma.nix10
-rw-r--r--nixos/modules/services/networking/pppd.nix7
-rw-r--r--nixos/modules/services/networking/prayer.nix90
-rw-r--r--nixos/modules/services/networking/prosody.nix11
-rw-r--r--nixos/modules/services/networking/quassel.nix9
-rw-r--r--nixos/modules/services/networking/quicktun.nix146
-rw-r--r--nixos/modules/services/networking/radvd.nix9
-rw-r--r--nixos/modules/services/networking/rosenpass.nix234
-rw-r--r--nixos/modules/services/networking/routedns.nix7
-rw-r--r--nixos/modules/services/networking/rxe.nix2
-rw-r--r--nixos/modules/services/networking/sabnzbd.nix7
-rw-r--r--nixos/modules/services/networking/seafile.nix7
-rw-r--r--nixos/modules/services/networking/searx.nix36
-rw-r--r--nixos/modules/services/networking/shellhub-agent.nix2
-rw-r--r--nixos/modules/services/networking/sing-box.nix2
-rw-r--r--nixos/modules/services/networking/skydns.nix7
-rw-r--r--nixos/modules/services/networking/smokeping.nix7
-rw-r--r--nixos/modules/services/networking/snowflake-proxy.nix2
-rw-r--r--nixos/modules/services/networking/softether.nix9
-rw-r--r--nixos/modules/services/networking/soju.nix1
-rw-r--r--nixos/modules/services/networking/spacecookie.nix11
-rw-r--r--nixos/modules/services/networking/spiped.nix5
-rw-r--r--nixos/modules/services/networking/squid.nix9
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix151
-rw-r--r--nixos/modules/services/networking/sslh.nix189
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/module.nix40
-rw-r--r--nixos/modules/services/networking/strongswan.nix1
-rw-r--r--nixos/modules/services/networking/syncplay.nix1
-rw-r--r--nixos/modules/services/networking/syncthing.nix82
-rw-r--r--nixos/modules/services/networking/tailscale.nix14
-rw-r--r--nixos/modules/services/networking/tayga.nix7
-rw-r--r--nixos/modules/services/networking/teamspeak3.nix42
-rw-r--r--nixos/modules/services/networking/tedicross.nix100
-rw-r--r--nixos/modules/services/networking/teleport.nix8
-rw-r--r--nixos/modules/services/networking/thelounge.nix2
-rw-r--r--nixos/modules/services/networking/tinc.nix9
-rw-r--r--nixos/modules/services/networking/tinyproxy.nix103
-rw-r--r--nixos/modules/services/networking/tmate-ssh-server.nix13
-rw-r--r--nixos/modules/services/networking/tox-bootstrapd.nix2
-rw-r--r--nixos/modules/services/networking/trickster.nix9
-rw-r--r--nixos/modules/services/networking/trust-dns.nix15
-rw-r--r--nixos/modules/services/networking/twingate.nix2
-rw-r--r--nixos/modules/services/networking/ucarp.nix9
-rw-r--r--nixos/modules/services/networking/unbound.nix35
-rw-r--r--nixos/modules/services/networking/unifi.nix89
-rw-r--r--nixos/modules/services/networking/v2ray.nix9
-rw-r--r--nixos/modules/services/networking/vdirsyncer.nix10
-rw-r--r--nixos/modules/services/networking/wasabibackend.nix1
-rw-r--r--nixos/modules/services/networking/webhook.nix2
-rw-r--r--nixos/modules/services/networking/websockify.nix2
-rw-r--r--nixos/modules/services/networking/wg-quick.nix2
-rw-r--r--nixos/modules/services/networking/wireguard.nix12
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix6
-rw-r--r--nixos/modules/services/networking/wstunnel.nix6
-rw-r--r--nixos/modules/services/networking/x2goserver.nix3
-rw-r--r--nixos/modules/services/networking/xandikos.nix7
-rw-r--r--nixos/modules/services/networking/xray.nix9
-rw-r--r--nixos/modules/services/networking/xrdp.nix186
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix48
-rw-r--r--nixos/modules/services/networking/zeronet.nix10
-rw-r--r--nixos/modules/services/networking/zerotierone.nix32
-rw-r--r--nixos/modules/services/networking/znc/default.nix1
-rw-r--r--nixos/modules/services/printing/cupsd.nix27
-rw-r--r--nixos/modules/services/search/elasticsearch.nix7
-rw-r--r--nixos/modules/services/search/hound.nix84
-rw-r--r--nixos/modules/services/search/kibana.nix213
-rw-r--r--nixos/modules/services/search/meilisearch.nix9
-rw-r--r--nixos/modules/services/search/opensearch.nix21
-rw-r--r--nixos/modules/services/search/sonic-server.nix77
-rw-r--r--nixos/modules/services/search/typesense.nix4
-rw-r--r--nixos/modules/services/security/authelia.nix7
-rw-r--r--nixos/modules/services/security/bitwarden-directory-connector-cli.nix323
-rw-r--r--nixos/modules/services/security/certmgr.nix8
-rw-r--r--nixos/modules/services/security/clamav.nix158
-rw-r--r--nixos/modules/services/security/esdm.nix2
-rw-r--r--nixos/modules/services/security/fail2ban.nix20
-rw-r--r--nixos/modules/services/security/haka.nix9
-rw-r--r--nixos/modules/services/security/hockeypuck.nix2
-rw-r--r--nixos/modules/services/security/jitterentropy-rngd.nix18
-rw-r--r--nixos/modules/services/security/kanidm.nix2
-rw-r--r--nixos/modules/services/security/munge.nix14
-rw-r--r--nixos/modules/services/security/nginx-sso.nix9
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix10
-rw-r--r--nixos/modules/services/security/opensnitch.nix2
-rw-r--r--nixos/modules/services/security/pass-secret-service.nix8
-rw-r--r--nixos/modules/services/security/privacyidea.nix458
-rw-r--r--nixos/modules/services/security/shibboleth-sp.nix33
-rw-r--r--nixos/modules/services/security/sks.nix7
-rw-r--r--nixos/modules/services/security/tang.nix95
-rw-r--r--nixos/modules/services/security/tor.nix9
-rw-r--r--nixos/modules/services/security/usbguard.nix31
-rw-r--r--nixos/modules/services/security/vault-agent.nix2
-rw-r--r--nixos/modules/services/security/vault.nix7
-rw-r--r--nixos/modules/services/security/vaultwarden/backup.sh4
-rw-r--r--nixos/modules/services/security/vaultwarden/default.nix21
-rw-r--r--nixos/modules/services/security/yubikey-agent.nix9
-rw-r--r--nixos/modules/services/system/automatic-timezoned.nix9
-rw-r--r--nixos/modules/services/system/bpftune.nix2
-rw-r--r--nixos/modules/services/system/cachix-agent/default.nix8
-rw-r--r--nixos/modules/services/system/cachix-watch-store.nix21
-rw-r--r--nixos/modules/services/system/cloud-init.nix5
-rw-r--r--nixos/modules/services/system/dbus.nix6
-rw-r--r--nixos/modules/services/system/earlyoom.nix2
-rw-r--r--nixos/modules/services/system/kerberos/default.nix2
-rw-r--r--nixos/modules/services/system/kerberos/heimdal.nix8
-rw-r--r--nixos/modules/services/system/kerberos/mit.nix2
-rw-r--r--nixos/modules/services/system/nix-daemon.nix5
-rw-r--r--nixos/modules/services/system/saslauthd.nix7
-rw-r--r--nixos/modules/services/system/systembus-notify.nix2
-rw-r--r--nixos/modules/services/system/zram-generator.nix4
-rw-r--r--nixos/modules/services/torrent/deluge.nix8
-rw-r--r--nixos/modules/services/torrent/flexget.nix5
-rw-r--r--nixos/modules/services/torrent/opentracker.nix9
-rw-r--r--nixos/modules/services/torrent/rtorrent.nix9
-rw-r--r--nixos/modules/services/torrent/torrentstream.nix53
-rw-r--r--nixos/modules/services/torrent/transmission.nix43
-rw-r--r--nixos/modules/services/video/epgstation/default.nix8
-rw-r--r--nixos/modules/services/video/frigate.nix10
-rw-r--r--nixos/modules/services/video/go2rtc/default.nix5
-rw-r--r--nixos/modules/services/video/mediamtx.nix4
-rw-r--r--nixos/modules/services/video/unifi-video.nix31
-rw-r--r--nixos/modules/services/web-apps/akkoma.nix34
-rw-r--r--nixos/modules/services/web-apps/alps.nix1
-rw-r--r--nixos/modules/services/web-apps/anuko-time-tracker.nix2
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix20
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix20
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix20
-rw-r--r--nixos/modules/services/web-apps/audiobookshelf.nix90
-rw-r--r--nixos/modules/services/web-apps/c2fmzq-server.md42
-rw-r--r--nixos/modules/services/web-apps/c2fmzq-server.nix130
-rw-r--r--nixos/modules/services/web-apps/calibre-web.nix7
-rw-r--r--nixos/modules/services/web-apps/cloudlog.nix2
-rw-r--r--nixos/modules/services/web-apps/code-server.nix1
-rw-r--r--nixos/modules/services/web-apps/coder.nix19
-rw-r--r--nixos/modules/services/web-apps/dex.nix7
-rw-r--r--nixos/modules/services/web-apps/discourse.nix4
-rw-r--r--nixos/modules/services/web-apps/documize.nix9
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix91
-rw-r--r--nixos/modules/services/web-apps/dolibarr.nix6
-rw-r--r--nixos/modules/services/web-apps/engelsystem.nix9
-rw-r--r--nixos/modules/services/web-apps/ethercalc.nix7
-rw-r--r--nixos/modules/services/web-apps/fluidd.nix7
-rw-r--r--nixos/modules/services/web-apps/freshrss.nix14
-rw-r--r--nixos/modules/services/web-apps/galene.nix11
-rw-r--r--nixos/modules/services/web-apps/gerrit.nix14
-rw-r--r--nixos/modules/services/web-apps/gotosocial.nix6
-rw-r--r--nixos/modules/services/web-apps/grocy.nix20
-rw-r--r--nixos/modules/services/web-apps/guacamole-client.nix2
-rw-r--r--nixos/modules/services/web-apps/guacamole-server.nix2
-rw-r--r--nixos/modules/services/web-apps/healthchecks.nix53
-rw-r--r--nixos/modules/services/web-apps/hedgedoc.nix1226
-rw-r--r--nixos/modules/services/web-apps/hledger-web.nix2
-rw-r--r--nixos/modules/services/web-apps/honk.nix4
-rw-r--r--nixos/modules/services/web-apps/invidious.nix317
-rw-r--r--nixos/modules/services/web-apps/invoiceplane.nix88
-rw-r--r--nixos/modules/services/web-apps/isso.nix4
-rw-r--r--nixos/modules/services/web-apps/jirafeau.nix7
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix222
-rw-r--r--nixos/modules/services/web-apps/kavita.nix2
-rw-r--r--nixos/modules/services/web-apps/keycloak.nix24
-rw-r--r--nixos/modules/services/web-apps/lanraragi.nix93
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix6
-rw-r--r--nixos/modules/services/web-apps/mainsail.nix7
-rw-r--r--nixos/modules/services/web-apps/mastodon.nix159
-rw-r--r--nixos/modules/services/web-apps/matomo.nix11
-rw-r--r--nixos/modules/services/web-apps/mattermost.nix20
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix137
-rw-r--r--nixos/modules/services/web-apps/meme-bingo-web.nix88
-rw-r--r--nixos/modules/services/web-apps/microbin.nix93
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix37
-rw-r--r--nixos/modules/services/web-apps/mobilizon.nix449
-rw-r--r--nixos/modules/services/web-apps/moodle.nix13
-rw-r--r--nixos/modules/services/web-apps/netbox.nix35
-rw-r--r--nixos/modules/services/web-apps/nextcloud.md14
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix699
-rw-r--r--nixos/modules/services/web-apps/node-red.nix7
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix9
-rw-r--r--nixos/modules/services/web-apps/openvscode-server.nix3
-rw-r--r--nixos/modules/services/web-apps/openwebrx.nix7
-rw-r--r--nixos/modules/services/web-apps/outline.nix83
-rw-r--r--nixos/modules/services/web-apps/peering-manager.nix239
-rw-r--r--nixos/modules/services/web-apps/peertube.nix139
-rw-r--r--nixos/modules/services/web-apps/pgpkeyserver-lite.nix9
-rw-r--r--nixos/modules/services/web-apps/photoprism.nix2
-rw-r--r--nixos/modules/services/web-apps/phylactery.nix9
-rw-r--r--nixos/modules/services/web-apps/pict-rs.nix8
-rw-r--r--nixos/modules/services/web-apps/pixelfed.nix5
-rw-r--r--nixos/modules/services/web-apps/plantuml-server.nix118
-rw-r--r--nixos/modules/services/web-apps/plausible.nix68
-rw-r--r--nixos/modules/services/web-apps/rimgo.nix107
-rw-r--r--nixos/modules/services/web-apps/sftpgo.nix9
-rw-r--r--nixos/modules/services/web-apps/shiori.nix16
-rw-r--r--nixos/modules/services/web-apps/slskd.nix2
-rw-r--r--nixos/modules/services/web-apps/snipe-it.nix18
-rw-r--r--nixos/modules/services/web-apps/suwayomi-server.md108
-rw-r--r--nixos/modules/services/web-apps/suwayomi-server.nix260
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix59
-rw-r--r--nixos/modules/services/web-apps/vikunja.nix18
-rw-r--r--nixos/modules/services/web-apps/whitebophir.nix7
-rw-r--r--nixos/modules/services/web-apps/windmill.nix177
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix17
-rw-r--r--nixos/modules/services/web-apps/writefreely.nix2
-rw-r--r--nixos/modules/services/web-apps/youtrack.nix9
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix9
-rw-r--r--nixos/modules/services/web-apps/zitadel.nix223
-rw-r--r--nixos/modules/services/web-servers/agate.nix8
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix18
-rw-r--r--nixos/modules/services/web-servers/caddy/default.nix24
-rw-r--r--nixos/modules/services/web-servers/garage.nix25
-rw-r--r--nixos/modules/services/web-servers/hydron.nix2
-rw-r--r--nixos/modules/services/web-servers/jboss/builder.sh2
-rw-r--r--nixos/modules/services/web-servers/keter/default.nix2
-rw-r--r--nixos/modules/services/web-servers/lighttpd/default.nix16
-rw-r--r--nixos/modules/services/web-servers/mighttpd2.nix5
-rw-r--r--nixos/modules/services/web-servers/minio.nix8
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix107
-rw-r--r--nixos/modules/services/web-servers/nginx/location-options.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/tailscale-auth.nix158
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix44
-rw-r--r--nixos/modules/services/web-servers/phpfpm/default.nix9
-rw-r--r--nixos/modules/services/web-servers/rustus.nix2
-rw-r--r--nixos/modules/services/web-servers/stargazer.nix8
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix165
-rw-r--r--nixos/modules/services/web-servers/traefik.nix8
-rw-r--r--nixos/modules/services/web-servers/ttyd.nix3
-rw-r--r--nixos/modules/services/web-servers/unit/default.nix7
-rw-r--r--nixos/modules/services/web-servers/varnish/default.nix9
-rw-r--r--nixos/modules/services/x11/desktop-managers/budgie.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix31
-rw-r--r--nixos/modules/services/x11/desktop-managers/deepin.nix41
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix4
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.md2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome.nix35
-rw-r--r--nixos/modules/services/x11/desktop-managers/kodi.nix10
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix3
-rw-r--r--nixos/modules/services/x11/desktop-managers/mate.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix7
-rw-r--r--nixos/modules/services/x11/desktop-managers/phosh.nix10
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix58
-rw-r--r--nixos/modules/services/x11/desktop-managers/retroarch.nix8
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix4
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix18
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix10
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix33
-rw-r--r--nixos/modules/services/x11/display-managers/xpra.nix6
-rw-r--r--nixos/modules/services/x11/extra-layouts.nix46
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix6
-rw-r--r--nixos/modules/services/x11/imwheel.nix2
-rw-r--r--nixos/modules/services/x11/picom.nix2
-rw-r--r--nixos/modules/services/x11/redshift.nix9
-rw-r--r--nixos/modules/services/x11/touchegg.nix7
-rw-r--r--nixos/modules/services/x11/unclutter-xfixes.nix7
-rw-r--r--nixos/modules/services/x11/unclutter.nix7
-rw-r--r--nixos/modules/services/x11/urxvtd.nix9
-rw-r--r--nixos/modules/services/x11/window-managers/awesome.nix7
-rw-r--r--nixos/modules/services/x11/window-managers/bspwm.nix20
-rw-r--r--nixos/modules/services/x11/window-managers/clfswm.nix9
-rw-r--r--nixos/modules/services/x11/window-managers/dk.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/dwm.nix10
-rw-r--r--nixos/modules/services/x11/window-managers/herbstluftwm.nix9
-rw-r--r--nixos/modules/services/x11/window-managers/i3.nix24
-rw-r--r--nixos/modules/services/x11/window-managers/katriawm.nix4
-rw-r--r--nixos/modules/services/x11/window-managers/oroborus.nix25
-rw-r--r--nixos/modules/services/x11/window-managers/qtile.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/ragnarwm.nix9
-rw-r--r--nixos/modules/services/x11/xscreensaver.nix40
-rw-r--r--nixos/modules/services/x11/xserver.nix119
-rw-r--r--nixos/modules/system/activation/activatable-system.nix65
-rw-r--r--nixos/modules/system/activation/activation-script.nix28
-rw-r--r--nixos/modules/system/activation/bootspec.nix4
-rwxr-xr-xnixos/modules/system/activation/switch-to-configuration.pl40
-rw-r--r--nixos/modules/system/activation/switchable-system.nix55
-rw-r--r--nixos/modules/system/boot/binfmt.nix55
-rw-r--r--nixos/modules/system/boot/clevis.md51
-rw-r--r--nixos/modules/system/boot/clevis.nix107
-rw-r--r--nixos/modules/system/boot/grow-partition.nix56
-rw-r--r--nixos/modules/system/boot/initrd-network.nix16
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix17
-rw-r--r--nixos/modules/system/boot/kernel.nix12
-rw-r--r--nixos/modules/system/boot/loader/external/external.nix2
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix2
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix14
-rw-r--r--[-rwxr-xr-x]nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py287
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix22
-rw-r--r--nixos/modules/system/boot/luksroot.nix58
-rw-r--r--nixos/modules/system/boot/networkd.nix193
-rw-r--r--nixos/modules/system/boot/resolved.nix41
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh5
-rw-r--r--nixos/modules/system/boot/stage-1.nix21
-rwxr-xr-xnixos/modules/system/boot/stage-2-init.sh4
-rw-r--r--nixos/modules/system/boot/systemd.nix231
-rw-r--r--nixos/modules/system/boot/systemd/homed.nix2
-rw-r--r--nixos/modules/system/boot/systemd/initrd-secrets.nix3
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix63
-rw-r--r--nixos/modules/system/boot/systemd/journald-gateway.nix135
-rw-r--r--nixos/modules/system/boot/systemd/journald-remote.nix163
-rw-r--r--nixos/modules/system/boot/systemd/journald-upload.nix111
-rw-r--r--nixos/modules/system/boot/systemd/journald.nix32
-rw-r--r--nixos/modules/system/boot/systemd/oomd.nix26
-rw-r--r--nixos/modules/system/boot/systemd/repart.nix12
-rw-r--r--nixos/modules/system/boot/systemd/sysupdate.nix2
-rw-r--r--nixos/modules/system/boot/systemd/sysusers.nix169
-rw-r--r--nixos/modules/system/boot/systemd/tmpfiles.nix139
-rw-r--r--nixos/modules/system/boot/systemd/userdbd.nix2
-rw-r--r--nixos/modules/system/boot/timesyncd.nix52
-rw-r--r--nixos/modules/system/boot/uki.nix85
-rw-r--r--nixos/modules/system/boot/unl0kr.nix89
-rw-r--r--nixos/modules/system/etc/build-composefs-dump.py209
-rwxr-xr-xnixos/modules/system/etc/check-build-composefs-dump.sh8
-rw-r--r--nixos/modules/system/etc/etc-activation.nix98
-rw-r--r--nixos/modules/system/etc/etc.nix116
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix12
-rw-r--r--nixos/modules/tasks/bcache.nix12
-rw-r--r--nixos/modules/tasks/encrypted-devices.nix69
-rw-r--r--nixos/modules/tasks/filesystems.nix12
-rw-r--r--nixos/modules/tasks/filesystems/bcachefs.nix145
-rw-r--r--nixos/modules/tasks/filesystems/btrfs.nix17
-rw-r--r--nixos/modules/tasks/filesystems/cifs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/ext.nix2
-rw-r--r--nixos/modules/tasks/filesystems/f2fs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/jfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/reiserfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/vfat.nix2
-rw-r--r--nixos/modules/tasks/filesystems/xfs.nix2
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix105
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix69
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix130
-rw-r--r--nixos/modules/tasks/network-interfaces.nix103
-rw-r--r--nixos/modules/tasks/swraid.nix25
-rw-r--r--nixos/modules/tasks/trackpoint.nix11
-rw-r--r--nixos/modules/testing/test-instrumentation.nix158
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix1
-rw-r--r--nixos/modules/virtualisation/anbox.nix59
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix46
-rw-r--r--nixos/modules/virtualisation/azure-image.nix46
-rw-r--r--nixos/modules/virtualisation/docker-rootless.nix9
-rw-r--r--nixos/modules/virtualisation/docker.nix9
-rw-r--r--nixos/modules/virtualisation/ecs-agent.nix7
-rw-r--r--nixos/modules/virtualisation/google-compute-config.nix6
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix13
-rw-r--r--nixos/modules/virtualisation/incus.nix239
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix84
-rw-r--r--nixos/modules/virtualisation/lxc-container.nix75
-rw-r--r--nixos/modules/virtualisation/lxc.nix26
-rw-r--r--nixos/modules/virtualisation/lxcfs.nix12
-rw-r--r--nixos/modules/virtualisation/lxd-agent.nix18
-rw-r--r--nixos/modules/virtualisation/lxd-virtual-machine.nix4
-rw-r--r--nixos/modules/virtualisation/lxd.nix149
-rw-r--r--nixos/modules/virtualisation/multipass.nix2
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix259
-rw-r--r--nixos/modules/virtualisation/oci-common.nix60
-rw-r--r--nixos/modules/virtualisation/oci-config-user.nix12
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix60
-rw-r--r--nixos/modules/virtualisation/oci-image.nix50
-rw-r--r--nixos/modules/virtualisation/oci-options.nix14
-rw-r--r--nixos/modules/virtualisation/openvswitch.nix9
-rw-r--r--nixos/modules/virtualisation/podman/default.nix33
-rw-r--r--nixos/modules/virtualisation/qemu-guest-agent.nix7
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix89
-rw-r--r--nixos/modules/virtualisation/vagrant-guest.nix1
-rw-r--r--nixos/modules/virtualisation/virtualbox-host.nix11
-rw-r--r--nixos/modules/virtualisation/vmware-host.nix68
-rw-r--r--nixos/modules/virtualisation/waydroid.nix20
-rw-r--r--nixos/release-combined.nix24
-rw-r--r--nixos/release.nix19
-rw-r--r--nixos/tests/3proxy.nix1
-rw-r--r--nixos/tests/acme.nix45
-rw-r--r--nixos/tests/activation/etc-overlay-immutable.nix30
-rw-r--r--nixos/tests/activation/etc-overlay-mutable.nix30
-rw-r--r--nixos/tests/activation/nix-channel.nix26
-rw-r--r--nixos/tests/activation/perlless.nix24
-rw-r--r--nixos/tests/activation/var.nix18
-rw-r--r--nixos/tests/adguardhome.nix2
-rw-r--r--nixos/tests/akkoma.nix5
-rw-r--r--nixos/tests/all-terminfo.nix6
-rw-r--r--nixos/tests/all-tests.nix142
-rw-r--r--nixos/tests/amazon-ssm-agent.nix17
-rw-r--r--nixos/tests/amd-sev.nix56
-rw-r--r--nixos/tests/anbox.nix36
-rw-r--r--nixos/tests/angie-api.nix148
-rw-r--r--nixos/tests/anki-sync-server.nix71
-rw-r--r--nixos/tests/appliance-repart-image.nix33
-rw-r--r--nixos/tests/archi.nix31
-rw-r--r--nixos/tests/audiobookshelf.nix23
-rw-r--r--nixos/tests/auth-mysql.nix3
-rw-r--r--nixos/tests/avahi.nix2
-rw-r--r--nixos/tests/ayatana-indicators.nix110
-rw-r--r--nixos/tests/babeld.nix4
-rw-r--r--nixos/tests/bcachefs.nix3
-rw-r--r--nixos/tests/bittorrent.nix6
-rw-r--r--nixos/tests/bootspec.nix31
-rw-r--r--nixos/tests/btrbk-section-order.nix7
-rw-r--r--nixos/tests/btrbk.nix1
-rw-r--r--nixos/tests/buildbot.nix13
-rw-r--r--nixos/tests/c2fmzq.nix82
-rw-r--r--nixos/tests/caddy.nix15
-rw-r--r--nixos/tests/castopod.nix87
-rw-r--r--nixos/tests/centrifugo.nix80
-rw-r--r--nixos/tests/ceph-single-node.nix21
-rw-r--r--nixos/tests/cinnamon-wayland.nix77
-rw-r--r--nixos/tests/cinnamon.nix66
-rw-r--r--nixos/tests/cloud-init.nix1
-rw-r--r--nixos/tests/cockpit.nix3
-rw-r--r--nixos/tests/common/auto-format-root-device.nix6
-rw-r--r--nixos/tests/containers-custom-pkgs.nix2
-rw-r--r--nixos/tests/containers-imperative.nix5
-rw-r--r--nixos/tests/containers-ip.nix5
-rw-r--r--nixos/tests/convos.nix1
-rw-r--r--nixos/tests/corerad.nix2
-rw-r--r--nixos/tests/curl-impersonate.nix2
-rw-r--r--nixos/tests/dae.nix33
-rw-r--r--nixos/tests/dconf.nix4
-rw-r--r--nixos/tests/deconz.nix28
-rw-r--r--nixos/tests/deepin.nix6
-rw-r--r--nixos/tests/dex-oidc.nix2
-rw-r--r--nixos/tests/dhparams.nix2
-rw-r--r--nixos/tests/discourse.nix2
-rw-r--r--nixos/tests/dnscrypt-wrapper/default.nix144
-rw-r--r--nixos/tests/docker-registry.nix2
-rw-r--r--nixos/tests/docker-tools.nix4
-rw-r--r--nixos/tests/documize.nix2
-rw-r--r--nixos/tests/dolibarr.nix2
-rw-r--r--nixos/tests/drawterm.nix58
-rw-r--r--nixos/tests/dublin-traceroute.nix63
-rw-r--r--nixos/tests/elk.nix20
-rw-r--r--nixos/tests/eris-server.nix2
-rw-r--r--nixos/tests/fanout.nix30
-rw-r--r--nixos/tests/fastnetmon-advanced.nix65
-rw-r--r--nixos/tests/ferm.nix2
-rw-r--r--nixos/tests/ferretdb.nix64
-rw-r--r--nixos/tests/firefox.nix30
-rw-r--r--nixos/tests/fontconfig-default-fonts.nix2
-rw-r--r--nixos/tests/forgejo.nix178
-rw-r--r--nixos/tests/freetube.nix41
-rw-r--r--nixos/tests/freshrss-pgsql.nix4
-rw-r--r--nixos/tests/frp.nix85
-rw-r--r--nixos/tests/frr.nix2
-rw-r--r--nixos/tests/ft2-clone.nix4
-rw-r--r--nixos/tests/garage/basic.nix6
-rw-r--r--nixos/tests/garage/default.nix5
-rw-r--r--nixos/tests/garage/with-3node-replication.nix6
-rw-r--r--nixos/tests/geoserver.nix24
-rw-r--r--nixos/tests/gitdaemon.nix3
-rw-r--r--nixos/tests/gitea.nix10
-rw-r--r--nixos/tests/gitlab.nix2
-rw-r--r--nixos/tests/gnome-extensions.nix151
-rw-r--r--nixos/tests/gnome-flashback.nix16
-rw-r--r--nixos/tests/gnome-xorg.nix16
-rw-r--r--nixos/tests/gnome.nix14
-rw-r--r--nixos/tests/gns3-server.nix55
-rw-r--r--nixos/tests/google-oslogin/default.nix3
-rw-r--r--nixos/tests/goss.nix53
-rw-r--r--nixos/tests/gotify-server.nix2
-rw-r--r--nixos/tests/grafana/basic.nix2
-rw-r--r--nixos/tests/grafana/provision/default.nix17
-rw-r--r--nixos/tests/grow-partition.nix83
-rw-r--r--nixos/tests/guix/basic.nix42
-rw-r--r--nixos/tests/guix/default.nix8
-rw-r--r--nixos/tests/guix/publish.nix96
-rw-r--r--nixos/tests/guix/scripts/add-existing-files-to-store.scm52
-rw-r--r--nixos/tests/guix/scripts/create-file-to-store.scm8
-rw-r--r--nixos/tests/gvisor.nix36
-rw-r--r--nixos/tests/hadoop/hadoop.nix26
-rw-r--r--nixos/tests/hadoop/hdfs.nix4
-rw-r--r--nixos/tests/haproxy.nix109
-rw-r--r--nixos/tests/hardened.nix2
-rw-r--r--nixos/tests/harmonia.nix3
-rw-r--r--nixos/tests/hedgedoc.nix62
-rw-r--r--nixos/tests/hockeypuck.nix2
-rw-r--r--nixos/tests/home-assistant.nix37
-rw-r--r--nixos/tests/hostname.nix1
-rw-r--r--nixos/tests/hydra/default.nix2
-rw-r--r--nixos/tests/incron.nix6
-rw-r--r--nixos/tests/incus/container.nix111
-rw-r--r--nixos/tests/incus/default.nix13
-rw-r--r--nixos/tests/incus/lxd-to-incus.nix112
-rw-r--r--nixos/tests/incus/preseed.nix62
-rw-r--r--nixos/tests/incus/socket-activated.nix28
-rw-r--r--nixos/tests/incus/virtual-machine.nix60
-rw-r--r--nixos/tests/initrd-network-openvpn/default.nix15
-rw-r--r--nixos/tests/input-remapper.nix5
-rw-r--r--nixos/tests/installed-tests/flatpak.nix1
-rw-r--r--nixos/tests/installer-systemd-stage-1.nix15
-rw-r--r--nixos/tests/installer.nix369
-rw-r--r--nixos/tests/invidious.nix121
-rw-r--r--nixos/tests/invoiceplane.nix104
-rw-r--r--nixos/tests/iscsi-multipath-root.nix4
-rw-r--r--nixos/tests/iscsi-root.nix4
-rw-r--r--nixos/tests/jitsi-meet.nix26
-rw-r--r--nixos/tests/jool.nix106
-rw-r--r--nixos/tests/kafka.nix85
-rw-r--r--nixos/tests/kanidm.nix1
-rw-r--r--nixos/tests/kea.nix51
-rw-r--r--nixos/tests/keepalived.nix3
-rw-r--r--nixos/tests/kerberos/heimdal.nix23
-rw-r--r--nixos/tests/kerberos/mit.nix23
-rw-r--r--nixos/tests/kernel-generic.nix4
-rw-r--r--nixos/tests/keyd.nix23
-rw-r--r--nixos/tests/keymap.nix30
-rw-r--r--nixos/tests/knot.nix140
-rw-r--r--nixos/tests/krb5/default.nix1
-rw-r--r--nixos/tests/krb5/deprecated-config.nix50
-rw-r--r--nixos/tests/krb5/example-config.nix140
-rw-r--r--nixos/tests/kubo/default.nix7
-rw-r--r--nixos/tests/kubo/kubo-fuse.nix42
-rw-r--r--nixos/tests/kubo/kubo.nix (renamed from nixos/tests/kubo.nix)39
-rw-r--r--nixos/tests/lanraragi.nix38
-rw-r--r--nixos/tests/legit.nix2
-rw-r--r--nixos/tests/lemmy.nix2
-rw-r--r--nixos/tests/librenms.nix108
-rw-r--r--nixos/tests/libvirtd.nix2
-rw-r--r--nixos/tests/lighttpd.nix1
-rw-r--r--nixos/tests/litestream.nix24
-rw-r--r--nixos/tests/livebook-service.nix43
-rw-r--r--nixos/tests/lvm2/systemd-stage-1.nix4
-rw-r--r--nixos/tests/lvm2/thinpool.nix2
-rw-r--r--nixos/tests/lvm2/vdo.nix4
-rw-r--r--nixos/tests/lxd-image-server.nix4
-rw-r--r--nixos/tests/lxd/container.nix20
-rw-r--r--nixos/tests/lxd/default.nix4
-rw-r--r--nixos/tests/lxd/nftables.nix6
-rw-r--r--nixos/tests/lxd/preseed.nix71
-rw-r--r--nixos/tests/lxd/ui.nix37
-rw-r--r--nixos/tests/lxd/virtual-machine.nix4
-rw-r--r--nixos/tests/mailman.nix6
-rw-r--r--nixos/tests/mate.nix27
-rw-r--r--nixos/tests/matrix/synapse-workers.nix50
-rw-r--r--nixos/tests/matrix/synapse.nix44
-rw-r--r--nixos/tests/mediawiki.nix16
-rw-r--r--nixos/tests/miriway.nix2
-rw-r--r--nixos/tests/misc.nix4
-rw-r--r--nixos/tests/mobilizon.nix44
-rw-r--r--nixos/tests/mongodb.nix2
-rw-r--r--nixos/tests/mosquitto.nix28
-rw-r--r--nixos/tests/munin.nix2
-rw-r--r--nixos/tests/musescore.nix7
-rw-r--r--nixos/tests/mympd.nix27
-rw-r--r--nixos/tests/mysql/common.nix3
-rw-r--r--nixos/tests/mysql/mariadb-galera.nix4
-rw-r--r--nixos/tests/mysql/mysql-backup.nix3
-rw-r--r--nixos/tests/mysql/mysql-replication.nix4
-rw-r--r--nixos/tests/mysql/mysql.nix14
-rw-r--r--nixos/tests/netdata.nix4
-rw-r--r--nixos/tests/networking.nix13
-rw-r--r--nixos/tests/nextcloud/basic.nix12
-rw-r--r--nixos/tests/nextcloud/default.nix6
-rw-r--r--nixos/tests/nextcloud/openssl-sse.nix109
-rw-r--r--nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix12
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix2
-rw-r--r--nixos/tests/nfs/kerberos.nix23
-rw-r--r--nixos/tests/nginx-etag-compression.nix45
-rw-r--r--nixos/tests/nginx-http3.nix182
-rw-r--r--nixos/tests/nginx-moreheaders.nix37
-rw-r--r--nixos/tests/nginx-redirectcode.nix25
-rw-r--r--nixos/tests/nginx-sandbox.nix65
-rw-r--r--nixos/tests/nginx-tmpdir.nix60
-rw-r--r--nixos/tests/nginx-unix-socket.nix27
-rw-r--r--nixos/tests/nginx-variants.nix10
-rw-r--r--nixos/tests/nitter.nix21
-rw-r--r--nixos/tests/nixops/default.nix2
-rw-r--r--nixos/tests/nixos-rebuild-install-bootloader.nix73
-rw-r--r--nixos/tests/nixos-rebuild-specialisations.nix2
-rw-r--r--nixos/tests/nixos-rebuild-target-host.nix136
-rw-r--r--nixos/tests/nixos-test-driver/timeout.nix15
-rw-r--r--nixos/tests/nixseparatedebuginfod.nix80
-rw-r--r--nixos/tests/non-switchable-system.nix15
-rw-r--r--nixos/tests/noto-fonts.nix2
-rw-r--r--nixos/tests/npmrc.nix22
-rw-r--r--nixos/tests/ntfy-sh-migration.nix77
-rw-r--r--nixos/tests/ntpd-rs.nix51
-rw-r--r--nixos/tests/oci-containers.nix2
-rw-r--r--nixos/tests/ocsinventory-agent.nix33
-rw-r--r--nixos/tests/openresty-lua.nix48
-rw-r--r--nixos/tests/opensearch.nix11
-rw-r--r--nixos/tests/opensmtpd-rspamd.nix1
-rw-r--r--nixos/tests/opensmtpd.nix1
-rw-r--r--nixos/tests/openssh.nix111
-rw-r--r--nixos/tests/opentabletdriver.nix4
-rw-r--r--nixos/tests/os-prober.nix2
-rw-r--r--nixos/tests/osquery.nix2
-rw-r--r--nixos/tests/owncast.nix2
-rw-r--r--nixos/tests/pam/pam-file-contents.nix2
-rw-r--r--nixos/tests/pam/pam-u2f.nix2
-rw-r--r--nixos/tests/pam/test_chfn.py9
-rw-r--r--nixos/tests/pantheon.nix50
-rw-r--r--nixos/tests/paperless.nix93
-rw-r--r--nixos/tests/pgadmin4.nix73
-rw-r--r--nixos/tests/pgbouncer.nix10
-rw-r--r--nixos/tests/pgjwt.nix2
-rw-r--r--nixos/tests/plantuml-server.nix20
-rw-r--r--nixos/tests/plausible.nix11
-rw-r--r--nixos/tests/pleroma.nix9
-rw-r--r--nixos/tests/podman/default.nix2
-rw-r--r--nixos/tests/postgis.nix7
-rw-r--r--nixos/tests/postgresql.nix4
-rw-r--r--nixos/tests/powerdns-admin.nix4
-rw-r--r--nixos/tests/predictable-interface-names.nix2
-rw-r--r--nixos/tests/printing.nix2
-rw-r--r--nixos/tests/privacyidea.nix43
-rw-r--r--nixos/tests/prometheus-exporters.nix146
-rw-r--r--nixos/tests/prometheus.nix14
-rw-r--r--nixos/tests/promscale.nix2
-rw-r--r--nixos/tests/prowlarr.nix4
-rw-r--r--nixos/tests/qemu-vm-external-disk-image.nix73
-rw-r--r--nixos/tests/qemu-vm-restrictnetwork.nix2
-rw-r--r--nixos/tests/qgis.nix30
-rw-r--r--nixos/tests/quicktun.nix18
-rw-r--r--nixos/tests/restic.nix50
-rw-r--r--nixos/tests/rkvm/cert.pem18
-rw-r--r--nixos/tests/rkvm/default.nix104
-rw-r--r--nixos/tests/rkvm/key.pem28
-rw-r--r--nixos/tests/rosenpass.nix217
-rw-r--r--nixos/tests/rspamd-trainer.nix155
-rw-r--r--nixos/tests/rss2email.nix1
-rw-r--r--nixos/tests/sabnzbd.nix3
-rw-r--r--nixos/tests/seatd.nix51
-rw-r--r--nixos/tests/sftpgo.nix20
-rw-r--r--nixos/tests/sgt-puzzles.nix (renamed from nixos/tests/sgtpuzzles.nix)4
-rw-r--r--nixos/tests/shadow.nix2
-rw-r--r--nixos/tests/shattered-pixel-dungeon.nix4
-rw-r--r--nixos/tests/slimserver.nix47
-rw-r--r--nixos/tests/snmpd.nix23
-rw-r--r--nixos/tests/soft-serve.nix102
-rw-r--r--nixos/tests/sogo.nix2
-rw-r--r--nixos/tests/sonic-server.nix22
-rw-r--r--nixos/tests/sourcehut.nix6
-rw-r--r--nixos/tests/spark/default.nix66
-rw-r--r--nixos/tests/sqlite3-to-mysql.nix2
-rw-r--r--nixos/tests/ssh-agent-auth.nix55
-rw-r--r--nixos/tests/ssh-audit.nix104
-rw-r--r--nixos/tests/sslh.nix18
-rw-r--r--nixos/tests/stratis/encryption.nix4
-rw-r--r--nixos/tests/stub-ld.nix73
-rw-r--r--nixos/tests/stunnel.nix15
-rw-r--r--nixos/tests/sudo-rs.nix95
-rw-r--r--nixos/tests/sudo.nix5
-rw-r--r--nixos/tests/suwayomi-server.nix46
-rw-r--r--nixos/tests/sway.nix2
-rw-r--r--nixos/tests/switch-test.nix122
-rw-r--r--nixos/tests/syncthing-init.nix1
-rw-r--r--nixos/tests/syncthing-many-devices.nix203
-rw-r--r--nixos/tests/sysinit-reactivation.nix107
-rw-r--r--nixos/tests/systemd-boot.nix69
-rw-r--r--nixos/tests/systemd-credentials-tpm2.nix59
-rw-r--r--nixos/tests/systemd-cryptenroll.nix61
-rw-r--r--nixos/tests/systemd-initrd-bridge.nix63
-rw-r--r--nixos/tests/systemd-initrd-luks-tpm2.nix27
-rw-r--r--nixos/tests/systemd-initrd-luks-unl0kr.nix75
-rw-r--r--nixos/tests/systemd-initrd-modprobe.nix7
-rw-r--r--nixos/tests/systemd-initrd-networkd-ssh.nix52
-rw-r--r--nixos/tests/systemd-initrd-networkd.nix133
-rw-r--r--nixos/tests/systemd-initrd-simple.nix12
-rw-r--r--nixos/tests/systemd-initrd-swraid.nix16
-rw-r--r--nixos/tests/systemd-initrd-vlan.nix59
-rw-r--r--nixos/tests/systemd-journal-gateway.nix90
-rw-r--r--nixos/tests/systemd-journal-upload.nix101
-rw-r--r--nixos/tests/systemd-journal.nix8
-rw-r--r--nixos/tests/systemd-networkd-dhcpserver.nix3
-rw-r--r--nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix8
-rw-r--r--nixos/tests/systemd-networkd.nix2
-rw-r--r--nixos/tests/systemd-nspawn.nix1
-rw-r--r--nixos/tests/systemd-repart.nix10
-rw-r--r--nixos/tests/systemd-sysusers-immutable.nix64
-rw-r--r--nixos/tests/systemd-sysusers-mutable.nix71
-rw-r--r--nixos/tests/systemd-timesyncd-nscd-dnssec.nix61
-rw-r--r--nixos/tests/systemd-timesyncd.nix14
-rw-r--r--nixos/tests/systemd.nix13
-rw-r--r--nixos/tests/systemtap.nix50
-rw-r--r--nixos/tests/tandoor-recipes.nix4
-rw-r--r--nixos/tests/tang.nix81
-rw-r--r--nixos/tests/tayga.nix2
-rw-r--r--nixos/tests/teleport.nix3
-rw-r--r--nixos/tests/terminal-emulators.nix8
-rw-r--r--nixos/tests/timescaledb.nix2
-rw-r--r--nixos/tests/tinyproxy.nix20
-rw-r--r--nixos/tests/tinywl.nix2
-rw-r--r--nixos/tests/tomcat.nix18
-rw-r--r--nixos/tests/tracee.nix4
-rw-r--r--nixos/tests/trafficserver.nix1
-rw-r--r--nixos/tests/transmission.nix3
-rw-r--r--nixos/tests/tsja.nix32
-rw-r--r--nixos/tests/tsm-client-gui.nix6
-rw-r--r--nixos/tests/typesense.nix3
-rw-r--r--nixos/tests/udisks2.nix3
-rw-r--r--nixos/tests/ulogd.nix82
-rw-r--r--nixos/tests/ulogd/ulogd.nix56
-rw-r--r--nixos/tests/ulogd/ulogd.py49
-rw-r--r--nixos/tests/unbound.nix16
-rw-r--r--nixos/tests/upnp.nix7
-rw-r--r--nixos/tests/uptermd.nix1
-rw-r--r--nixos/tests/varnish.nix6
-rw-r--r--nixos/tests/vaultwarden.nix5
-rw-r--r--nixos/tests/vikunja.nix2
-rw-r--r--nixos/tests/watchdogd.nix22
-rw-r--r--nixos/tests/web-apps/mastodon/remote-postgresql.nix22
-rw-r--r--nixos/tests/web-apps/mastodon/script.nix3
-rw-r--r--nixos/tests/web-apps/mastodon/standard.nix4
-rw-r--r--nixos/tests/web-apps/netbox-upgrade.nix10
-rw-r--r--nixos/tests/web-apps/netbox.nix1
-rw-r--r--nixos/tests/web-servers/stargazer.nix108
-rw-r--r--nixos/tests/web-servers/ttyd.nix19
-rw-r--r--nixos/tests/wiki-js.nix5
-rw-r--r--nixos/tests/wordpress.nix2
-rw-r--r--nixos/tests/wrappers.nix24
-rw-r--r--nixos/tests/xfce.nix56
-rw-r--r--nixos/tests/xmpp/ejabberd.nix2
-rw-r--r--nixos/tests/xrdp-with-audio-pulseaudio.nix97
-rw-r--r--nixos/tests/xscreensaver.nix64
-rw-r--r--nixos/tests/yggdrasil.nix1
-rw-r--r--nixos/tests/zammad.nix9
-rw-r--r--nixos/tests/zfs.nix16
-rw-r--r--nixos/tests/zrepl.nix1
-rw-r--r--nixos/tests/zwave-js.nix31
1427 files changed, 41450 insertions, 14875 deletions
diff --git a/nixos/README.md b/nixos/README.md
index b3cd9d234fa61..07e82bf0ad938 100644
--- a/nixos/README.md
+++ b/nixos/README.md
@@ -8,6 +8,27 @@ https://nixos.org/nixos and in the manual in doc/manual.
 
 You can add new module to your NixOS configuration file (usually it’s `/etc/nixos/configuration.nix`). And do `sudo nixos-rebuild test -I nixpkgs=<path to your local nixpkgs folder> --fast`.
 
+## Commit conventions
+
+- Make sure you read about the [commit conventions](../CONTRIBUTING.md#commit-conventions) common to Nixpkgs as a whole.
+
+- Format the commit messages in the following way:
+
+  ```
+  nixos/(module): (init module | add setting | refactor | etc)
+
+  (Motivation for change. Link to release notes. Additional information.)
+  ```
+
+  Examples:
+
+  * nixos/hydra: add bazBaz option
+
+    Dual baz behavior is needed to do foo.
+  * nixos/nginx: refactor config generation
+
+    The old config generation system used impure shell scripts and could break in specific circumstances (see #1234).
+
 ## Reviewing contributions
 
 When changing the bootloader installation process, extra care must be taken. Grub installations cannot be rolled back, hence changes may break people’s installations forever. For any non-trivial change to the bootloader please file a PR asking for review, especially from \@edolstra.
@@ -21,12 +42,14 @@ Reviewing process:
 - Ensure that the module maintainers are notified.
   - [CODEOWNERS](https://help.github.com/articles/about-codeowners/) will make GitHub notify users based on the submitted changes, but it can happen that it misses some of the package maintainers.
 - Ensure that the module tests, if any, are succeeding.
+  - You may invoke OfBorg with `@ofborg test <module>` to build `nixosTests.<module>`
 - Ensure that the introduced options are correct.
   - Type should be appropriate (string related types differs in their merging capabilities, `loaOf` and `string` types are deprecated).
   - Description, default and example should be provided.
 - Ensure that option changes are backward compatible.
-  - `mkRenamedOptionModuleWith` provides a way to make option changes backward compatible.
-- Ensure that removed options are declared with `mkRemovedOptionModule`
+  - `mkRenamedOptionModuleWith` provides a way to make renamed option backward compatible.
+  - Use `lib.versionAtLeast config.system.stateVersion "23.11"` on backward incompatible changes which may corrupt, change or update the state stored on existing setups.
+- Ensure that removed options are declared with `mkRemovedOptionModule`.
 - Ensure that changes that are not backward compatible are mentioned in release notes.
 - Ensure that documentations affected by the change is updated.
 
@@ -55,6 +78,7 @@ New modules submissions introduce a new module to NixOS.
 
 Reviewing process:
 
+- Ensure that all file paths [fit the guidelines](../CONTRIBUTING.md#file-naming-and-organisation).
 - Ensure that the module tests, if any, are succeeding.
 - Ensure that the introduced options are correct.
   - Type should be appropriate (string related types differs in their merging capabilities, `loaOf` and `string` types are deprecated).
@@ -76,9 +100,9 @@ Sample template for a new module review is provided below.
 - [ ] options have default
 - [ ] options have example
 - [ ] options have descriptions
-- [ ] No unneeded package is added to environment.systemPackages
-- [ ] meta.maintainers is set
-- [ ] module documentation is declared in meta.doc
+- [ ] No unneeded package is added to `environment.systemPackages`
+- [ ] `meta.maintainers` is set
+- [ ] module documentation is declared in `meta.doc`
 
 ##### Possible improvements
 
diff --git a/nixos/doc/manual/administration/imperative-containers.section.md b/nixos/doc/manual/administration/imperative-containers.section.md
index f45991780c4b9..852305ad81486 100644
--- a/nixos/doc/manual/administration/imperative-containers.section.md
+++ b/nixos/doc/manual/administration/imperative-containers.section.md
@@ -77,7 +77,7 @@ Linux foo 3.4.82 #1-NixOS SMP Thu Mar 20 14:44:05 UTC 2014 x86_64 GNU/Linux
 
 There are several ways to change the configuration of the container.
 First, on the host, you can edit
-`/var/lib/container/name/etc/nixos/configuration.nix`, and run
+`/var/lib/nixos-containers/foo/etc/nixos/configuration.nix`, and run
 
 ```ShellSession
 # nixos-container update foo
diff --git a/nixos/doc/manual/administration/nixos-state.section.md b/nixos/doc/manual/administration/nixos-state.section.md
new file mode 100644
index 0000000000000..9819d613198c3
--- /dev/null
+++ b/nixos/doc/manual/administration/nixos-state.section.md
@@ -0,0 +1,28 @@
+# NixOS {#sec-nixos-state}
+
+## `/nix` {#sec-state-nix}
+
+NixOS needs the entirety of `/nix` to be persistent, as it includes:
+- `/nix/store`, which contains all the system's executables, libraries, and supporting data;
+- `/nix/var/nix`, which contains:
+  - the Nix daemon's database;
+  - roots whose transitive closure is preserved when garbage-collecting the Nix store;
+  - system-wide and per-user profiles.
+
+## `/boot` {#sec-state-boot}
+
+`/boot` should also be persistent, as it contains:
+- the kernel and initrd which the bootloader loads,
+- the bootloader's configuration, including the kernel's command-line which
+  determines the store path to use as system environment.
+
+
+## Users and groups {#sec-state-users}
+
+- `/var/lib/nixos` should persist: it holds state needed to generate stable
+  uids and gids for declaratively-managed users and groups, etc.
+- `users.mutableUsers` should be false, *or* the following files under `/etc`
+  should all persist:
+  - {manpage}`passwd(5)` and {manpage}`group(5)`,
+  - {manpage}`shadow(5)` and {manpage}`gshadow(5)`,
+  - {manpage}`subuid(5)` and {manpage}`subgid(5)`.
diff --git a/nixos/doc/manual/administration/running.md b/nixos/doc/manual/administration/running.md
index 48e8c7c6668b7..83412d9b7af58 100644
--- a/nixos/doc/manual/administration/running.md
+++ b/nixos/doc/manual/administration/running.md
@@ -8,6 +8,7 @@ rebooting.chapter.md
 user-sessions.chapter.md
 control-groups.chapter.md
 logging.chapter.md
+system-state.chapter.md
 cleaning-store.chapter.md
 containers.chapter.md
 troubleshooting.chapter.md
diff --git a/nixos/doc/manual/administration/system-state.chapter.md b/nixos/doc/manual/administration/system-state.chapter.md
new file mode 100644
index 0000000000000..6840cc3902578
--- /dev/null
+++ b/nixos/doc/manual/administration/system-state.chapter.md
@@ -0,0 +1,17 @@
+# Necessary system state {#ch-system-state}
+
+Normally — on systems with a persistent `rootfs` — system services can persist state to
+the filesystem without administrator intervention.
+
+However, it is possible and not-uncommon to create [impermanent systems], whose
+`rootfs` is either a `tmpfs` or reset during boot. While NixOS itself supports
+this kind of configuration, special care needs to be taken.
+
+[impermanent systems]: https://nixos.wiki/wiki/Impermanence
+
+
+```{=include=} sections
+nixos-state.section.md
+systemd-state.section.md
+zfs-state.section.md
+```
diff --git a/nixos/doc/manual/administration/systemd-state.section.md b/nixos/doc/manual/administration/systemd-state.section.md
new file mode 100644
index 0000000000000..84f074871a655
--- /dev/null
+++ b/nixos/doc/manual/administration/systemd-state.section.md
@@ -0,0 +1,52 @@
+# systemd {#sec-systemd-state}
+
+## `machine-id(5)` {#sec-machine-id}
+
+`systemd` uses per-machine identifier — {manpage}`machine-id(5)` — which must be
+unique and persistent; otherwise, the system journal may fail to list earlier
+boots, etc.
+
+`systemd` generates a random `machine-id(5)` during boot if it does not already exist,
+and persists it in `/etc/machine-id`.  As such, it suffices to make that file persistent.
+
+Alternatively, it is possible to generate a random `machine-id(5)`; while the
+specification allows for *any* hex-encoded 128b value, systemd itself uses
+[UUIDv4], *i.e.* random UUIDs, and it is thus preferable to do so as well, in
+case some software assumes `machine-id(5)` to be a UUIDv4. Those can be
+generated with `uuidgen -r | tr -d -` (`tr` being used to remove the dashes).
+
+Such a `machine-id(5)` can be set by writing it to `/etc/machine-id` or through
+the kernel's command-line, though NixOS' systemd maintainers [discourage] the
+latter approach.
+
+[UUIDv4]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)
+[discourage]: https://github.com/NixOS/nixpkgs/pull/268995
+
+
+## `/var/lib/systemd` {#sec-var-systemd}
+
+Moreover, `systemd` expects its state directory — `/var/lib/systemd` — to persist, for:
+- {manpage}`systemd-random-seed(8)`, which loads a 256b “seed” into the kernel's RNG
+  at boot time, and saves a fresh one during shutdown;
+- {manpage}`systemd.timer(5)` with `Persistent=yes`, which are then run after boot if
+  the timer would have triggered during the time the system was shut down;
+- {manpage}`systemd-coredump(8)` to store core dumps there by default;
+  (see {manpage}`coredump.conf(5)`)
+- {manpage}`systemd-timesyncd(8)`;
+- {manpage}`systemd-backlight(8)` and {manpage}`systemd-rfkill(8)` persist hardware-related
+  state;
+- possibly other things, this list is not meant to be exhaustive.
+
+In any case, making `/var/lib/systemd` persistent is recommended.
+
+
+## `/var/log/journal/{machine-id}` {#sec-var-journal}
+
+Lastly, {manpage}`systemd-journald(8)` writes the system's journal in binary
+form to `/var/log/journal/{machine-id}`; if (locally) persisting the entire log
+is desired, it is recommended to make all of `/var/log/journal` persistent.
+
+If not, one can set `Storage=volatile` in {manpage}`journald.conf(5)`
+([`services.journald.storage = "volatile";`](#opt-services.journald.storage)),
+which disables journal persistence and causes it to be written to
+`/run/log/journal`.
diff --git a/nixos/doc/manual/administration/zfs-state.section.md b/nixos/doc/manual/administration/zfs-state.section.md
new file mode 100644
index 0000000000000..11ad5badea7ed
--- /dev/null
+++ b/nixos/doc/manual/administration/zfs-state.section.md
@@ -0,0 +1,16 @@
+# ZFS {#sec-zfs-state}
+
+When using ZFS, `/etc/zfs/zpool.cache` should be persistent (or a symlink to a persistent
+location) as it is the default value for the `cachefile` [property](man:zpoolprops(7)).
+
+This cachefile is used on system startup to discover ZFS pools, so ZFS pools
+holding the `rootfs` and/or early-boot datasets such as `/nix` can be set to
+`cachefile=none`.
+
+In principle, if there are no other pools attached to the system, `zpool.cache`
+does not need to be persisted; it is however *strongly recommended* to persist
+it, in case additional pools are added later on, temporarily or permanently:
+
+While mishandling the cachefile does not lead to data loss by itself, it may
+cause zpools not to be imported during boot, and services may then write to a
+location where a dataset was expected to be mounted.
diff --git a/nixos/doc/manual/configuration/adding-custom-packages.section.md b/nixos/doc/manual/configuration/adding-custom-packages.section.md
index 89d329550613c..2340723e07c6b 100644
--- a/nixos/doc/manual/configuration/adding-custom-packages.section.md
+++ b/nixos/doc/manual/configuration/adding-custom-packages.section.md
@@ -44,7 +44,7 @@ environment.systemPackages =
       name = "hello-2.8";
       src = fetchurl {
         url = "mirror://gnu/hello/${name}.tar.gz";
-        sha256 = "0wqd8sjmxfskrflaxywc7gqw7sfawrfvdxd9skxawzfgyy0pzdz6";
+        hash = "sha256-5rd/gffPfa761Kn1tl3myunD8TuM+66oy1O7XqVGDXM=";
       };
     };
   in
@@ -67,7 +67,7 @@ stdenv.mkDerivation rec {
   name = "hello-2.8";
   src = fetchurl {
     url = "mirror://gnu/hello/${name}.tar.gz";
-    sha256 = "0wqd8sjmxfskrflaxywc7gqw7sfawrfvdxd9skxawzfgyy0pzdz6";
+    hash = "sha256-5rd/gffPfa761Kn1tl3myunD8TuM+66oy1O7XqVGDXM=";
   };
 }
 ```
diff --git a/nixos/doc/manual/configuration/customizing-packages.section.md b/nixos/doc/manual/configuration/customizing-packages.section.md
index 709a07b09cead..76413b7d84fb8 100644
--- a/nixos/doc/manual/configuration/customizing-packages.section.md
+++ b/nixos/doc/manual/configuration/customizing-packages.section.md
@@ -1,11 +1,7 @@
 # Customising Packages {#sec-customising-packages}
 
 Some packages in Nixpkgs have options to enable or disable optional
-functionality or change other aspects of the package. For instance, the
-Firefox wrapper package (which provides Firefox with a set of plugins
-such as the Adobe Flash player) has an option to enable the Google Talk
-plugin. It can be set in `configuration.nix` as follows:
-`nixpkgs.config.firefox.enableGoogleTalkPlugin = true;`
+functionality or change other aspects of the package.
 
 ::: {.warning}
 Unfortunately, Nixpkgs currently lacks a way to query available
@@ -13,7 +9,7 @@ configuration options.
 :::
 
 ::: {.note}
-Alternatively, many packages come with extensions one might add.
+For example, many packages come with extensions one might add.
 Examples include:
 - [`passExtensions.pass-otp`](https://search.nixos.org/packages/query=passExtensions.pass-otp)
 - [`python310Packages.requests`](https://search.nixos.org/packages/query=python310Packages.requests)
diff --git a/nixos/doc/manual/configuration/declarative-packages.section.md b/nixos/doc/manual/configuration/declarative-packages.section.md
index 02eaa56192e46..480e250da8c73 100644
--- a/nixos/doc/manual/configuration/declarative-packages.section.md
+++ b/nixos/doc/manual/configuration/declarative-packages.section.md
@@ -37,7 +37,7 @@ Note: the `nixos` prefix tells us that we want to get the package from
 the `nixos` channel and works only in CLI tools. In declarative
 configuration use `pkgs` prefix (variable).
 
-To "uninstall" a package, simply remove it from
+To "uninstall" a package, remove it from
 [](#opt-environment.systemPackages) and run `nixos-rebuild switch`.
 
 ```{=include=} sections
diff --git a/nixos/doc/manual/configuration/gpu-accel.chapter.md b/nixos/doc/manual/configuration/gpu-accel.chapter.md
index 40878b5da4b57..aa63aec61669b 100644
--- a/nixos/doc/manual/configuration/gpu-accel.chapter.md
+++ b/nixos/doc/manual/configuration/gpu-accel.chapter.md
@@ -26,7 +26,7 @@ directory which is scanned by the ICL loader for ICD files. For example:
 
 ```ShellSession
 $ export \
-  OCL_ICD_VENDORS=`nix-build '<nixpkgs>' --no-out-link -A rocm-opencl-icd`/etc/OpenCL/vendors/
+  OCL_ICD_VENDORS=`nix-build '<nixpkgs>' --no-out-link -A rocmPackages.clr.icd`/etc/OpenCL/vendors/
 ```
 
 The second mechanism is to add the OpenCL driver package to
@@ -50,13 +50,13 @@ Platform Vendor      Advanced Micro Devices, Inc.
 
 Modern AMD [Graphics Core
 Next](https://en.wikipedia.org/wiki/Graphics_Core_Next) (GCN) GPUs are
-supported through the rocm-opencl-icd package. Adding this package to
+supported through the rocmPackages.clr.icd package. Adding this package to
 [](#opt-hardware.opengl.extraPackages)
 enables OpenCL support:
 
 ```nix
 hardware.opengl.extraPackages = [
-  rocm-opencl-icd
+  rocmPackages.clr.icd
 ];
 ```
 
@@ -65,12 +65,10 @@ hardware.opengl.extraPackages = [
 [Intel Gen8 and later
 GPUs](https://en.wikipedia.org/wiki/List_of_Intel_graphics_processing_units#Gen8)
 are supported by the Intel NEO OpenCL runtime that is provided by the
-intel-compute-runtime package. For Gen7 GPUs, the deprecated Beignet
-runtime can be used, which is provided by the beignet package. The
-proprietary Intel OpenCL runtime, in the intel-ocl package, is an
-alternative for Gen7 GPUs.
+intel-compute-runtime package. The proprietary Intel OpenCL runtime, in
+the intel-ocl package, is an alternative for Gen7 GPUs.
 
-The intel-compute-runtime, beignet, or intel-ocl package can be added to
+The intel-compute-runtime or intel-ocl package can be added to
 [](#opt-hardware.opengl.extraPackages)
 to enable OpenCL support. For example, for Gen8 and later GPUs, the following
 configuration can be used:
diff --git a/nixos/doc/manual/configuration/linux-kernel.chapter.md b/nixos/doc/manual/configuration/linux-kernel.chapter.md
index f5bce99dd1bbe..9d1b2bc2f9b8f 100644
--- a/nixos/doc/manual/configuration/linux-kernel.chapter.md
+++ b/nixos/doc/manual/configuration/linux-kernel.chapter.md
@@ -84,26 +84,7 @@ available parameters, run `sysctl -a`.
 
 ## Building a custom kernel {#sec-linux-config-customizing}
 
-You can customize the default kernel configuration by overriding the arguments for your kernel package:
-
-```nix
-pkgs.linux_latest.override {
-  ignoreConfigErrors = true;
-  autoModules = false;
-  kernelPreferBuiltin = true;
-  extraStructuredConfig = with lib.kernel; {
-    DEBUG_KERNEL = yes;
-    FRAME_POINTER = yes;
-    KGDB = yes;
-    KGDB_SERIAL_CONSOLE = yes;
-    DEBUG_INFO = yes;
-  };
-}
-```
-
-See `pkgs/os-specific/linux/kernel/generic.nix` for details on how these arguments
-affect the generated configuration. You can also build a custom version of Linux by calling
-`pkgs.buildLinux` directly, which requires the `src` and `version` arguments to be specified.
+Please refer to the Nixpkgs manual for the various ways of [building a custom kernel](https://nixos.org/nixpkgs/manual#sec-linux-kernel).
 
 To use your custom kernel package in your NixOS configuration, set
 
@@ -111,50 +92,9 @@ To use your custom kernel package in your NixOS configuration, set
 boot.kernelPackages = pkgs.linuxPackagesFor yourCustomKernel;
 ```
 
-Note that this method will use the common configuration defined in `pkgs/os-specific/linux/kernel/common-config.nix`,
-which is suitable for a NixOS system.
-
-If you already have a generated configuration file, you can build a kernel that uses it with `pkgs.linuxManualConfig`:
-
-```nix
-let
-  baseKernel = pkgs.linux_latest;
-in pkgs.linuxManualConfig {
-  inherit (baseKernel) src modDirVersion;
-  version = "${baseKernel.version}-custom";
-  configfile = ./my_kernel_config;
-  allowImportFromDerivation = true;
-}
-```
-
-::: {.note}
-The build will fail if `modDirVersion` does not match the source's `kernel.release` file,
-so `modDirVersion` should remain tied to `src`.
-:::
-
-To edit the `.config` file for Linux X.Y, proceed as follows:
-
-```ShellSession
-$ nix-shell '<nixpkgs>' -A linuxKernel.kernels.linux_X_Y.configEnv
-$ unpackPhase
-$ cd linux-*
-$ make nconfig
-```
-
 ## Developing kernel modules {#sec-linux-config-developing-modules}
 
-When developing kernel modules it's often convenient to run
-edit-compile-run loop as quickly as possible. See below snippet as an
-example of developing `mellanox` drivers.
-
-```ShellSession
-$ nix-build '<nixpkgs>' -A linuxPackages.kernel.dev
-$ nix-shell '<nixpkgs>' -A linuxPackages.kernel
-$ unpackPhase
-$ cd linux-*
-$ make -C $dev/lib/modules/*/build M=$(pwd)/drivers/net/ethernet/mellanox modules
-# insmod ./drivers/net/ethernet/mellanox/mlx5/core/mlx5_core.ko
-```
+This section was moved to the [Nixpkgs manual](https://nixos.org/nixpkgs/manual#sec-linux-kernel-developing-modules).
 
 ## ZFS {#sec-linux-zfs}
 
@@ -163,7 +103,7 @@ available Linux kernel. It is recommended to use the latest available LTS that's
 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:
+version _that is supported by ZFS_ like this:
 
 ```nix
 {
diff --git a/nixos/doc/manual/configuration/luks-file-systems.section.md b/nixos/doc/manual/configuration/luks-file-systems.section.md
index b5d0407d16595..7615b95aef422 100644
--- a/nixos/doc/manual/configuration/luks-file-systems.section.md
+++ b/nixos/doc/manual/configuration/luks-file-systems.section.md
@@ -42,8 +42,12 @@ boot.loader.grub.enableCryptodisk = true;
 
 ## FIDO2 {#sec-luks-file-systems-fido2}
 
-NixOS also supports unlocking your LUKS-Encrypted file system using a
-FIDO2 compatible token. In the following example, we will create a new
+NixOS also supports unlocking your LUKS-Encrypted file system using a FIDO2
+compatible token.
+
+### Without systemd in initrd {#sec-luks-file-systems-fido2-legacy}
+
+In the following example, we will create a new
 FIDO2 credential and add it as a new key to our existing device
 `/dev/sda2`:
 
@@ -75,3 +79,37 @@ as [Trezor](https://trezor.io/).
 ```nix
 boot.initrd.luks.devices."/dev/sda2".fido2.passwordLess = true;
 ```
+
+### systemd Stage 1 {#sec-luks-file-systems-fido2-systemd}
+
+If systemd stage 1 is enabled, it handles unlocking of LUKS-enrypted volumes
+during boot. The following example enables systemd stage1 and adds support for
+unlocking the existing LUKS2 volume `root` using any enrolled FIDO2 compatible
+tokens.
+
+```nix
+boot.initrd = {
+  luks.devices.root = {
+    crypttabExtraOpts = [ "fido2-device=auto" ];
+    device = "/dev/sda2";
+  };
+  systemd.enable = true;
+};
+```
+
+All tokens that should be used for unlocking the LUKS2-encrypted volume must
+first be enrolled using [systemd-cryptenroll](https://www.freedesktop.org/software/systemd/man/systemd-cryptenroll.html).
+In the following example, a new key slot for the first discovered token is
+added to the LUKS volume.
+
+```ShellSession
+# systemd-cryptenroll --fido2-device=auto /dev/sda2
+```
+
+Existing key slots are left intact, unless `--wipe-slot=` is specified. It is
+recommened to add a recovery key that should be stored in a secure physical
+location and can be entered wherever a password would be entered.
+
+```ShellSession
+# systemd-cryptenroll --recovery-key /dev/sda2
+```
diff --git a/nixos/doc/manual/configuration/modularity.section.md b/nixos/doc/manual/configuration/modularity.section.md
index 2eff15387987f..f4a566d669735 100644
--- a/nixos/doc/manual/configuration/modularity.section.md
+++ b/nixos/doc/manual/configuration/modularity.section.md
@@ -36,8 +36,8 @@ Here, we include two modules from the same directory, `vpn.nix` and
 Note that both `configuration.nix` and `kde.nix` define the option
 [](#opt-environment.systemPackages). When multiple modules define an
 option, NixOS will try to *merge* the definitions. In the case of
-[](#opt-environment.systemPackages), that's easy: the lists of
-packages can simply be concatenated. The value in `configuration.nix` is
+[](#opt-environment.systemPackages) the lists of packages will be
+concatenated. The value in `configuration.nix` is
 merged last, so for list-type options, it will appear at the end of the
 merged list. If you want it to appear first, you can use `mkBefore`:
 
diff --git a/nixos/doc/manual/configuration/profiles.chapter.md b/nixos/doc/manual/configuration/profiles.chapter.md
index 9f1f48f742ac5..9f6c11b0d59d5 100644
--- a/nixos/doc/manual/configuration/profiles.chapter.md
+++ b/nixos/doc/manual/configuration/profiles.chapter.md
@@ -29,6 +29,7 @@ profiles/graphical.section.md
 profiles/hardened.section.md
 profiles/headless.section.md
 profiles/installation-device.section.md
+profiles/perlless.section.md
 profiles/minimal.section.md
 profiles/qemu-guest.section.md
 ```
diff --git a/nixos/doc/manual/configuration/profiles/perlless.section.md b/nixos/doc/manual/configuration/profiles/perlless.section.md
new file mode 100644
index 0000000000000..bf055971cfc42
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/perlless.section.md
@@ -0,0 +1,11 @@
+# Perlless {#sec-perlless}
+
+::: {.warning}
+If you enable this profile, you will NOT be able to switch to a new
+configuration and thus you will not be able to rebuild your system with
+nixos-rebuild!
+:::
+
+Render your system completely perlless (i.e. without the perl interpreter). This
+includes a mechanism so that your build fails if it contains a Nix store path
+that references the string "perl".
diff --git a/nixos/doc/manual/configuration/subversion.chapter.md b/nixos/doc/manual/configuration/subversion.chapter.md
index 84f9c2703378a..ff870f5c40b97 100644
--- a/nixos/doc/manual/configuration/subversion.chapter.md
+++ b/nixos/doc/manual/configuration/subversion.chapter.md
@@ -2,7 +2,7 @@
 
 [Subversion](https://subversion.apache.org/) is a centralized
 version-control system. It can use a [variety of
-protocols](http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.choosing)
+protocols](https://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.choosing)
 for communication between client and server.
 
 ## Subversion inside Apache HTTP {#module-services-subversion-apache-httpd}
@@ -14,7 +14,7 @@ for communication.
 
 For more information on the general setup, please refer to the [the
 appropriate section of the Subversion
-book](http://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.httpd).
+book](https://svnbook.red-bean.com/en/1.7/svn-book.html#svn.serverconfig.httpd).
 
 To configure, include in `/etc/nixos/configuration.nix` code to activate
 Apache HTTP, setting [](#opt-services.httpd.adminAddr)
diff --git a/nixos/doc/manual/configuration/user-mgmt.chapter.md b/nixos/doc/manual/configuration/user-mgmt.chapter.md
index b35b38f6e964a..71d61ce4c641b 100644
--- a/nixos/doc/manual/configuration/user-mgmt.chapter.md
+++ b/nixos/doc/manual/configuration/user-mgmt.chapter.md
@@ -89,3 +89,18 @@ A user can be deleted using `userdel`:
 The flag `-r` deletes the user's home directory. Accounts can be
 modified using `usermod`. Unix groups can be managed using `groupadd`,
 `groupmod` and `groupdel`.
+
+## Create users and groups with `systemd-sysusers` {#sec-systemd-sysusers}
+
+::: {.note}
+This is experimental.
+:::
+
+Instead of using a custom perl script to create users and groups, you can use
+systemd-sysusers:
+
+```nix
+systemd.sysusers.enable = true;
+```
+
+The primary benefit of this is to remove a dependency on perl.
diff --git a/nixos/doc/manual/configuration/x-windows.chapter.md b/nixos/doc/manual/configuration/x-windows.chapter.md
index bef35f4488742..0451e4d25265f 100644
--- a/nixos/doc/manual/configuration/x-windows.chapter.md
+++ b/nixos/doc/manual/configuration/x-windows.chapter.md
@@ -45,8 +45,8 @@ services.xserver.displayManager.gdm.enable = true;
 You can set the keyboard layout (and optionally the layout variant):
 
 ```nix
-services.xserver.layout = "de";
-services.xserver.xkbVariant = "neo";
+services.xserver.xkb.layout = "de";
+services.xserver.xkb.variant = "neo";
 ```
 
 The X server is started automatically at boot time. If you don't want
@@ -208,7 +208,7 @@ qt.style = "gtk2";
 
 It is possible to install custom [ XKB
 ](https://en.wikipedia.org/wiki/X_keyboard_extension) keyboard layouts
-using the option `services.xserver.extraLayouts`.
+using the option `services.xserver.xkb.extraLayouts`.
 
 As a first example, we are going to create a layout based on the basic
 US layout, with an additional layer to type some greek symbols by
@@ -235,7 +235,7 @@ xkb_symbols "us-greek"
 A minimal layout specification must include the following:
 
 ```nix
-services.xserver.extraLayouts.us-greek = {
+services.xserver.xkb.extraLayouts.us-greek = {
   description = "US layout with alt-gr greek";
   languages   = [ "eng" ];
   symbolsFile = /yourpath/symbols/us-greek;
@@ -266,7 +266,7 @@ Once the configuration is applied, and you did a logout/login cycle, the
 layout should be ready to use. You can try it by e.g. running
 `setxkbmap us-greek` and then type `<alt>+a` (it may not get applied in
 your terminal straight away). To change the default, the usual
-`services.xserver.layout` option can still be used.
+`services.xserver.xkb.layout` option can still be used.
 
 A layout can have several other components besides `xkb_symbols`, for
 example we will define new keycodes for some multimedia key and bind
@@ -298,7 +298,7 @@ xkb_symbols "media"
 As before, to install the layout do
 
 ```nix
-services.xserver.extraLayouts.media = {
+services.xserver.xkb.extraLayouts.media = {
   description  = "Multimedia keys remapping";
   languages    = [ "eng" ];
   symbolsFile  = /path/to/media-key;
diff --git a/nixos/doc/manual/configuration/xfce.chapter.md b/nixos/doc/manual/configuration/xfce.chapter.md
index a80be2b523e24..9ec4a51d6e35e 100644
--- a/nixos/doc/manual/configuration/xfce.chapter.md
+++ b/nixos/doc/manual/configuration/xfce.chapter.md
@@ -28,7 +28,7 @@ manually (system wide), put them into your
 
 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
+option [](#opt-programs.thunar.enable) instead of adding
 `pkgs.xfce.thunar` to [](#opt-environment.systemPackages).
 
 If you'd like to add extra plugins to Thunar, add them to
diff --git a/nixos/doc/manual/development/activation-script.section.md b/nixos/doc/manual/development/activation-script.section.md
index c339258c6dc48..cc317a6a01aa8 100644
--- a/nixos/doc/manual/development/activation-script.section.md
+++ b/nixos/doc/manual/development/activation-script.section.md
@@ -69,4 +69,4 @@ do:
   `/etc/group` and `/etc/shadow`. This also creates home directories
 - `usrbinenv` creates `/usr/bin/env`
 - `var` creates some directories in `/var` that are not service-specific
-- `wrappers` creates setuid wrappers like `ping` and `sudo`
+- `wrappers` creates setuid wrappers like `sudo`
diff --git a/nixos/doc/manual/development/etc-overlay.section.md b/nixos/doc/manual/development/etc-overlay.section.md
new file mode 100644
index 0000000000000..e6f6d8d4ca1ef
--- /dev/null
+++ b/nixos/doc/manual/development/etc-overlay.section.md
@@ -0,0 +1,36 @@
+# `/etc` via overlay filesystem {#sec-etc-overlay}
+
+::: {.note}
+This is experimental and requires a kernel version >= 6.6 because it uses
+new overlay features and relies on the new mount API.
+:::
+
+Instead of using a custom perl script to activate `/etc`, you activate it via an
+overlay filesystem:
+
+```nix
+system.etc.overlay.enable = true;
+```
+
+Using an overlay has two benefits:
+
+1. it removes a dependency on perl
+2. it makes activation faster (up to a few seconds)
+
+By default, the `/etc` overlay is mounted writable (i.e. there is a writable
+upper layer). However, you can also mount `/etc` immutably (i.e. read-only) by
+setting:
+
+```nix
+system.etc.overlay.mutable = false;
+```
+
+The overlay is atomically replaced during system switch. However, files that
+have been modified will NOT be overwritten. This is the biggest change compared
+to the perl-based system.
+
+If you manually make changes to `/etc` on your system and then switch to a new
+configuration where `system.etc.overlay.mutable = false;`, you will not be able
+to see the previously made changes in `/etc` anymore. However the changes are
+not completely gone, they are still in the upperdir of the previous overlay in
+`/.rw-etc/upper`.
diff --git a/nixos/doc/manual/development/non-switchable-systems.section.md b/nixos/doc/manual/development/non-switchable-systems.section.md
new file mode 100644
index 0000000000000..87bb46c789091
--- /dev/null
+++ b/nixos/doc/manual/development/non-switchable-systems.section.md
@@ -0,0 +1,21 @@
+# Non Switchable Systems {#sec-non-switchable-system}
+
+In certain systems, most notably image based appliances, updates are handled
+outside the system. This means that you do not need to rebuild your
+configuration on the system itself anymore.
+
+If you want to build such a system, you can use the `image-based-appliance`
+profile:
+
+```nix
+{ modulesPath, ... }: {
+  imports = [ "${modulesPath}/profiles/image-based-appliance.nix" ]
+}
+```
+
+The most notable deviation of this profile from a standard NixOS configuration
+is that after building it, you cannot switch *to* the configuration anymore.
+The profile sets `config.system.switch.enable = false;`, which excludes
+`switch-to-configuration`, the central script called by `nixos-rebuild`, from
+your system. Removing this script makes the image lighter and slightly more
+secure.
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index 3448b07722b85..762070416187d 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixos/doc/manual/development/option-declarations.section.md
@@ -90,7 +90,7 @@ lib.mkOption {
 ```
 :::
 
-### `mkPackageOption`, `mkPackageOptionMD` {#sec-option-declarations-util-mkPackageOption}
+### `mkPackageOption` {#sec-option-declarations-util-mkPackageOption}
 
 Usage:
 
@@ -121,15 +121,13 @@ valid attribute path in pkgs (if name is a list).
 
 If you wish to explicitly provide no default, pass `null` as `default`.
 
-During the transition to CommonMark documentation `mkPackageOption` creates an option with a DocBook description attribute, once the transition is completed it will create a CommonMark description instead. `mkPackageOptionMD` always creates an option with a CommonMark description attribute and will be removed some time after the transition is completed.
-
 []{#ex-options-declarations-util-mkPackageOption}
 Examples:
 
 ::: {#ex-options-declarations-util-mkPackageOption-hello .example}
 ### Simple `mkPackageOption` usage
 ```nix
-lib.mkPackageOptionMD pkgs "hello" { }
+lib.mkPackageOption pkgs "hello" { }
 # is like
 lib.mkOption {
   type = lib.types.package;
@@ -143,7 +141,7 @@ lib.mkOption {
 ::: {#ex-options-declarations-util-mkPackageOption-ghc .example}
 ### `mkPackageOption` with explicit default and example
 ```nix
-lib.mkPackageOptionMD pkgs "GHC" {
+lib.mkPackageOption pkgs "GHC" {
   default = [ "ghc" ];
   example = "pkgs.haskell.packages.ghc92.ghc.withPackages (hkgs: [ hkgs.primes ])";
 }
diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md
index 44bb3b4782e19..f9c7ac80018e4 100644
--- a/nixos/doc/manual/development/option-types.section.md
+++ b/nixos/doc/manual/development/option-types.section.md
@@ -13,6 +13,13 @@ merging is handled.
 `types.bool`
 
 :   A boolean, its values can be `true` or `false`.
+    All definitions must have the same value, after priorities. An error is thrown in case of a conflict.
+
+`types.boolByOr`
+
+:   A boolean, its values can be `true` or `false`.
+    The result is `true` if _any_ of multiple definitions is `true`.
+    In other words, definitions are merged with the logical _OR_ operator.
 
 `types.path`
 
@@ -528,7 +535,7 @@ The only required parameter is `name`.
 
 :   A string representation of the type function name.
 
-`definition`
+`description`
 
 :   Description of the type used in documentation. Give information of
     the type and any of its arguments.
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 54002941d6349..4b8385d7e0d94 100644
--- a/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
+++ b/nixos/doc/manual/development/running-nixos-tests-interactively.section.md
@@ -57,6 +57,27 @@ using:
 Once the connection is established, you can enter commands in the socat terminal
 where socat is running.
 
+## Port forwarding to NixOS test VMs {#sec-nixos-test-port-forwarding}
+
+If your test has only a single VM, you may use e.g.
+
+```ShellSession
+$ QEMU_NET_OPTS="hostfwd=tcp:127.0.0.1:2222-:22" ./result/bin/nixos-test-driver
+```
+
+to port-forward a port in the VM (here `22`) to the host machine (here port `2222`).
+
+This naturally does not work when multiple machines are involved,
+since a single port on the host cannot forward to multiple VMs.
+
+If the test defines multiple machines, you may opt to _temporarily_ set
+`virtualisation.forwardPorts` in the test definition for debugging.
+
+Such port forwardings connect via the VM's virtual network interface.
+Thus they cannot connect to ports that are only bound to the VM's
+loopback interface (`127.0.0.1`), and the VM's NixOS firewall
+must be configured to allow these connections.
+
 ## Reuse VM state {#sec-nixos-test-reuse-vm-state}
 
 You can re-use the VM states coming from a previous run by setting the
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index 5060dd98f58fc..3a4800742b048 100644
--- a/nixos/doc/manual/development/settings-options.section.md
+++ b/nixos/doc/manual/development/settings-options.section.md
@@ -58,7 +58,7 @@ have a predefined type and string generator already declared under
     and returning a set with YAML-specific attributes `type` and
     `generate` as specified [below](#pkgs-formats-result).
 
-`pkgs.formats.ini` { *`listsAsDuplicateKeys`* ? false, *`listToValue`* ? null, \... }
+`pkgs.formats.ini` { *`listsAsDuplicateKeys`* ? false, *`listToValue`* ? null, \.\.\. }
 
 :   A function taking an attribute set with values
 
diff --git a/nixos/doc/manual/development/unit-handling.section.md b/nixos/doc/manual/development/unit-handling.section.md
index 32d44dbfff054..d5ba6a9529d01 100644
--- a/nixos/doc/manual/development/unit-handling.section.md
+++ b/nixos/doc/manual/development/unit-handling.section.md
@@ -63,3 +63,42 @@ checks:
     is **restart**ed with the others. If it is set, both the service and the
     socket are **stop**ped and the socket is **start**ed, leaving socket
     activation to start the service when it's needed.
+
+## Sysinit reactivation {#sec-sysinit-reactivation}
+
+[`sysinit.target`](https://www.freedesktop.org/software/systemd/man/latest/systemd.special.html#sysinit.target)
+is a systemd target that encodes system initialization (i.e. early startup). A
+few units that need to run very early in the bootup process are ordered to
+finish before this target is reached. Probably the most notable one of these is
+`systemd-tmpfiles-setup.service`. We will refer to these units as "sysinit
+units".
+
+"Normal" systemd units, by default, are ordered AFTER `sysinit.target`. In
+other words, these "normal" units expect all services ordered before
+`sysinit.target` to have finished without explicity declaring this dependency
+relationship for each dependency. See the [systemd
+bootup](https://www.freedesktop.org/software/systemd/man/latest/bootup.html)
+for more details on the bootup process.
+
+When restarting both a unit ordered before `sysinit.target` as well as one
+after, this presents a problem because they would be started at the same time
+as they do not explicitly declare their dependency relations.
+
+To solve this, NixOS has an artificial `sysinit-reactivation.target` which
+allows you to ensure that services ordered before `sysinit.target` are
+restarted correctly. This applies both to the ordering between these sysinit
+services as well as ensuring that sysinit units are restarted before "normal"
+units.
+
+To make an existing sysinit service restart correctly during system switch, you
+have to declare:
+
+```nix
+systemd.services.my-sysinit = {
+  requiredBy = [ "sysinit-reactivation.target" ];
+  before = [ "sysinit-reactivation.target" ];
+  restartTriggers = [ config.environment.etc."my-sysinit.d".source ];
+};
+```
+
+You need to configure appropriate `restartTriggers` specific to your service.
diff --git a/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md b/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
index 9cbec729803ac..28c06f999dac2 100644
--- a/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
+++ b/nixos/doc/manual/development/what-happens-during-a-system-switch.chapter.md
@@ -21,8 +21,9 @@ If the action is `switch` or `test`, the currently running system is inspected
 and the actions to switch to the new system are calculated. This process takes
 two data sources into account: `/etc/fstab` and the current systemd status.
 Mounts and swaps are read from `/etc/fstab` and the corresponding actions are
-generated. If a new mount is added, for example, the proper `.mount` unit is
-marked to be started. The current systemd state is inspected, the difference
+generated. If the options of a mount are modified, for example, the proper `.mount`
+unit is reloaded (or restarted if anything else changed and it's neither the root
+mount or the nix store). The current systemd state is inspected, the difference
 between the current system and the desired configuration is calculated and
 actions are generated to get to this state. There are a lot of nuances that can
 be controlled by the units which are explained here.
@@ -36,13 +37,17 @@ of actions is always the same:
 - Forget about the failed state of units (`systemctl reset-failed`)
 - Reload systemd (`systemctl daemon-reload`)
 - Reload systemd user instances (`systemctl --user daemon-reload`)
-- Set up tmpfiles (`systemd-tmpfiles --create`)
+- Reactivate sysinit (`systemctl restart sysinit-reactivation.target`)
 - Reload units (`systemctl reload`)
 - Restart units (`systemctl restart`)
 - Start units (`systemctl start`)
 - Inspect what changed during these actions and print units that failed and
   that were newly started
 
+By default, some units are filtered from the outputs to make it less spammy.
+This can be disabled for development or testing by setting the environment variable
+`STC_DISPLAY_ALL_UNITS=1`
+
 Most of these actions are either self-explaining but some of them have to do
 with our units or the activation script. For this reason, these topics are
 explained in the next sections.
@@ -50,4 +55,6 @@ explained in the next sections.
 ```{=include=} sections
 unit-handling.section.md
 activation-script.section.md
+non-switchable-systems.section.md
+etc-overlay.section.md
 ```
diff --git a/nixos/doc/manual/development/writing-documentation.chapter.md b/nixos/doc/manual/development/writing-documentation.chapter.md
index 8d504dfb0b0a0..3d9bd318cf339 100644
--- a/nixos/doc/manual/development/writing-documentation.chapter.md
+++ b/nixos/doc/manual/development/writing-documentation.chapter.md
@@ -16,7 +16,7 @@ You can quickly validate your edits with `make`:
 ```ShellSession
 $ cd /path/to/nixpkgs/nixos/doc/manual
 $ nix-shell
-nix-shell$ make
+nix-shell$ devmode
 ```
 
 Once you are done making modifications to the manual, it's important to
@@ -33,13 +33,13 @@ symlink at `./result/share/doc/nixos/index.html`.
 ## Editing DocBook XML {#sec-writing-docs-editing-docbook-xml}
 
 For general information on how to write in DocBook, see [DocBook 5: The
-Definitive Guide](http://www.docbook.org/tdg5/en/html/docbook.html).
+Definitive Guide](https://tdg.docbook.org/tdg/5.1/).
 
 Emacs nXML Mode is very helpful for editing DocBook XML because it
 validates the document as you write, and precisely locates errors. To
 use it, see [](#sec-emacs-docbook-xml).
 
-[Pandoc](http://pandoc.org) can generate DocBook XML from a multitude of
+[Pandoc](https://pandoc.org/) can generate DocBook XML from a multitude of
 formats, which makes a good starting point. Here is an example of Pandoc
 invocation to convert GitHub-Flavoured MarkDown to DocBook 5 XML:
 
@@ -50,7 +50,7 @@ pandoc -f markdown_github -t docbook5 docs.md -o my-section.md
 Pandoc can also quickly convert a single `section.xml` to HTML, which is
 helpful when drafting.
 
-Sometimes writing valid DocBook is simply too difficult. In this case,
+Sometimes writing valid DocBook is too difficult. In this case,
 submit your documentation updates in a [GitHub
 Issue](https://github.com/NixOS/nixpkgs/issues/new) and someone will
 handle the conversion to XML for you.
@@ -62,9 +62,9 @@ topic from scratch.
 
 Keep the following guidelines in mind when you create and add a topic:
 
--   The NixOS [`book`](http://www.docbook.org/tdg5/en/html/book.html)
+-   The NixOS [`book`](https://tdg.docbook.org/tdg/5.0/book.html)
     element is in `nixos/doc/manual/manual.xml`. It includes several
-    [`parts`](http://www.docbook.org/tdg5/en/html/book.html) which are in
+    [`parts`](https://tdg.docbook.org/tdg/5.0/book.html) which are in
     subdirectories.
 
 -   Store the topic file in the same directory as the `part` to which it
diff --git a/nixos/modules/image/repart.md b/nixos/doc/manual/installation/building-images-via-systemd-repart.chapter.md
index 6d0675f21a033..6d0675f21a033 100644
--- a/nixos/modules/image/repart.md
+++ b/nixos/doc/manual/installation/building-images-via-systemd-repart.chapter.md
diff --git a/nixos/doc/manual/installation/changing-config.chapter.md b/nixos/doc/manual/installation/changing-config.chapter.md
index 11b49ccb1f671..9e56b15a880f6 100644
--- a/nixos/doc/manual/installation/changing-config.chapter.md
+++ b/nixos/doc/manual/installation/changing-config.chapter.md
@@ -55,6 +55,14 @@ which causes the new configuration (and previous ones created using
 This can be useful to separate test configurations from "stable"
 configurations.
 
+A repl, or read-eval-print loop, is also available. You can inspect your configuration and use the Nix language with
+
+```ShellSession
+# nixos-rebuild repl
+```
+
+Your configuration is loaded into the `config` variable. Use tab for autocompletion, use the `:r` command to reload the configuration files. See `:?` or [`nix repl` in the Nix manual](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-repl.html) to learn more.
+
 Finally, you can do
 
 ```ShellSession
@@ -89,7 +97,7 @@ guest. For instance, the following will forward host port 2222 to guest
 port 22 (SSH):
 
 ```ShellSession
-$ QEMU_NET_OPTS="hostfwd=tcp::2222-:22" ./result/bin/run-*-vm
+$ QEMU_NET_OPTS="hostfwd=tcp:127.0.0.1:2222-:22" ./result/bin/run-*-vm
 ```
 
 allowing you to log in via SSH (assuming you have set the appropriate
@@ -98,3 +106,8 @@ passwords or SSH authorized keys):
 ```ShellSession
 $ ssh -p 2222 localhost
 ```
+
+Such port forwardings connect via the VM's virtual network interface.
+Thus they cannot connect to ports that are only bound to the VM's
+loopback interface (`127.0.0.1`), and the VM's NixOS firewall
+must be configured to allow these connections.
diff --git a/nixos/doc/manual/installation/installation.md b/nixos/doc/manual/installation/installation.md
index 140594256609f..f3b1773d865ce 100644
--- a/nixos/doc/manual/installation/installation.md
+++ b/nixos/doc/manual/installation/installation.md
@@ -8,4 +8,5 @@ installing.chapter.md
 changing-config.chapter.md
 upgrading.chapter.md
 building-nixos.chapter.md
+building-images-via-systemd-repart.chapter.md
 ```
diff --git a/nixos/doc/manual/installation/installing-pxe.section.md b/nixos/doc/manual/installation/installing-pxe.section.md
index 4fbd6525f8c3b..c1cad99d39f34 100644
--- a/nixos/doc/manual/installation/installing-pxe.section.md
+++ b/nixos/doc/manual/installation/installing-pxe.section.md
@@ -4,7 +4,7 @@ Advanced users may wish to install NixOS using an existing PXE or iPXE
 setup.
 
 These instructions assume that you have an existing PXE or iPXE
-infrastructure and simply want to add the NixOS installer as another
+infrastructure and want to add the NixOS installer as another
 option. To build the necessary files from your current version of nixpkgs,
 you can run:
 
diff --git a/nixos/doc/manual/installation/installing-usb.section.md b/nixos/doc/manual/installation/installing-usb.section.md
index adfe22ea2f00e..3b9e2f492f04d 100644
--- a/nixos/doc/manual/installation/installing-usb.section.md
+++ b/nixos/doc/manual/installation/installing-usb.section.md
@@ -35,7 +35,7 @@ select the image, select the USB flash drive and click "Write".
 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
+  sudo dd bs=4M conv=fsync oflag=direct status=progress if=<path-to-image> of=/dev/sdX
   ```
 
 ## Creating bootable USB flash drive from a Terminal on macOS {#sec-booting-from-usb-macos}
diff --git a/nixos/doc/manual/installation/upgrading.chapter.md b/nixos/doc/manual/installation/upgrading.chapter.md
index d39e1b786d835..79cd4e55be5cc 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-23.05`](https://channels.nixos.org/nixos-23.05).
+-   *Stable channels*, such as [`nixos-23.11`](https://channels.nixos.org/nixos-23.11).
     These only get conservative bug fixes and package upgrades. For
     instance, a channel update may cause the Linux kernel on your system
     to be upgraded from 4.19.34 to 4.19.38 (a minor bug fix), but not
@@ -19,7 +19,7 @@ passed and all packages have been built. These channels are:
     radical changes between channel updates. It's not recommended for
     production systems.
 
--   *Small channels*, such as [`nixos-23.05-small`](https://channels.nixos.org/nixos-23.05-small)
+-   *Small channels*, such as [`nixos-23.11-small`](https://channels.nixos.org/nixos-23.11-small)
     or [`nixos-unstable-small`](https://channels.nixos.org/nixos-unstable-small).
     These are identical to the stable and unstable channels described above,
     except that they contain fewer binary packages. This means they get updated
@@ -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 23.05 ISO, you will be subscribed to
-the `nixos-23.05` channel. To see which NixOS channel you're subscribed
+instance, if you installed from a 23.11 ISO, you will be subscribed to
+the `nixos-23.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 23.05 stable channel:
+use the NixOS 23.11 stable channel:
 
 ```ShellSession
-# nix-channel --add https://channels.nixos.org/nixos-23.05 nixos
+# nix-channel --add https://channels.nixos.org/nixos-23.11 nixos
 ```
 
 If you have a server, you may want to use the "small" channel instead:
 
 ```ShellSession
-# nix-channel --add https://channels.nixos.org/nixos-23.05-small nixos
+# nix-channel --add https://channels.nixos.org/nixos-23.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://channels.nixos.org/nixos-23.05";
+system.autoUpgrade.channel = "https://channels.nixos.org/nixos-23.11";
 ```
diff --git a/nixos/doc/manual/release-notes/release-notes.md b/nixos/doc/manual/release-notes/release-notes.md
index 3f926fb21a5ca..0514a1b0044af 100644
--- a/nixos/doc/manual/release-notes/release-notes.md
+++ b/nixos/doc/manual/release-notes/release-notes.md
@@ -3,6 +3,7 @@
 This section lists the release notes for each stable version of NixOS and current unstable revision.
 
 ```{=include=} sections
+rl-2405.section.md
 rl-2311.section.md
 rl-2305.section.md
 rl-2211.section.md
diff --git a/nixos/doc/manual/release-notes/rl-1509.section.md b/nixos/doc/manual/release-notes/rl-1509.section.md
index 1422ae4c299cd..f47d13008185e 100644
--- a/nixos/doc/manual/release-notes/rl-1509.section.md
+++ b/nixos/doc/manual/release-notes/rl-1509.section.md
@@ -2,7 +2,7 @@
 
 In addition to numerous new and upgraded packages, this release has the following highlights:
 
-- The [Haskell](http://haskell.org/) packages infrastructure has been re-designed from the ground up ("Haskell NG"). NixOS now distributes the latest version of every single package registered on [Hackage](http://hackage.haskell.org/) \-- well in excess of 8,000 Haskell packages. Detailed instructions on how to use that infrastructure can be found in the [User's Guide to the Haskell Infrastructure](https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure). Users migrating from an earlier release may find helpful information below, in the list of backwards-incompatible changes. Furthermore, we distribute 51(!) additional Haskell package sets that provide every single [LTS Haskell](http://www.stackage.org/) release since version 0.0 as well as the most recent [Stackage Nightly](http://www.stackage.org/) snapshot. The announcement ["Full Stackage Support in Nixpkgs"](https://nixos.org/nix-dev/2015-September/018138.html) gives additional details.
+- The [Haskell](http://haskell.org/) packages infrastructure has been re-designed from the ground up ("Haskell NG"). NixOS now distributes the latest version of every single package registered on [Hackage](http://hackage.haskell.org/) -- well in excess of 8,000 Haskell packages. Detailed instructions on how to use that infrastructure can be found in the [User's Guide to the Haskell Infrastructure](https://nixos.org/nixpkgs/manual/#users-guide-to-the-haskell-infrastructure). Users migrating from an earlier release may find helpful information below, in the list of backwards-incompatible changes. Furthermore, we distribute 51(!) additional Haskell package sets that provide every single [LTS Haskell](http://www.stackage.org/) release since version 0.0 as well as the most recent [Stackage Nightly](http://www.stackage.org/) snapshot. The announcement ["Full Stackage Support in Nixpkgs"](https://nixos.org/nix-dev/2015-September/018138.html) gives additional details.
 
 - Nix has been updated to version 1.10, which among other improvements enables cryptographic signatures on binary caches for improved security.
 
@@ -178,7 +178,7 @@ The new option `system.stateVersion` ensures that certain configuration changes
 
 - Nix now requires binary caches to be cryptographically signed. If you have unsigned binary caches that you want to continue to use, you should set `nix.requireSignedBinaryCaches = false`.
 
-- Steam now doesn't need root rights to work. Instead of using `*-steam-chrootenv`, you should now just run `steam`. `steamChrootEnv` package was renamed to `steam`, and old `steam` package \-- to `steamOriginal`.
+- Steam now doesn't need root rights to work. Instead of using `*-steam-chrootenv`, you should now just run `steam`. `steamChrootEnv` package was renamed to `steam`, and old `steam` package -- to `steamOriginal`.
 
 - CMPlayer has been renamed to bomi upstream. Package `cmplayer` was accordingly renamed to `bomi`
 
diff --git a/nixos/doc/manual/release-notes/rl-1609.section.md b/nixos/doc/manual/release-notes/rl-1609.section.md
index ad3478d0ca173..0cbabf58ca039 100644
--- a/nixos/doc/manual/release-notes/rl-1609.section.md
+++ b/nixos/doc/manual/release-notes/rl-1609.section.md
@@ -46,7 +46,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 Other notable improvements:
 
-- Revamped grsecurity/PaX support. There is now only a single general-purpose distribution kernel and the configuration interface has been streamlined. Desktop users should be able to simply set
+- Revamped grsecurity/PaX support. There is now only a single general-purpose distribution kernel and the configuration interface has been streamlined. Desktop users should be able to set
 
   ```nix
   {
diff --git a/nixos/doc/manual/release-notes/rl-1909.section.md b/nixos/doc/manual/release-notes/rl-1909.section.md
index 22cef05d4fa75..2bd04f8dd40a3 100644
--- a/nixos/doc/manual/release-notes/rl-1909.section.md
+++ b/nixos/doc/manual/release-notes/rl-1909.section.md
@@ -198,7 +198,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   For nginx, the dependencies are still automatically managed when `services.nginx.virtualhosts.<name>.enableACME` is enabled just like before. What changed is that nginx now directly depends on the specific certificates that it needs, instead of depending on the catch-all `acme-certificates.target`. This target unit was also removed from the codebase. This will mean nginx will no longer depend on certificates it isn't explicitly managing and fixes a bug with certificate renewal ordering racing with nginx restarting which could lead to nginx getting in a broken state as described at [NixOS/nixpkgs\#60180](https://github.com/NixOS/nixpkgs/issues/60180).
 
-- The old deprecated `emacs` package sets have been dropped. What used to be called `emacsPackagesNg` is now simply called `emacsPackages`.
+- The old deprecated `emacs` package sets have been dropped. What used to be called `emacsPackagesNg` is now called `emacsPackages`.
 
 - `services.xserver.desktopManager.xterm` is now disabled by default if `stateVersion` is 19.09 or higher. Previously the xterm desktopManager was enabled when xserver was enabled, but it isn't useful for all people so it didn't make sense to have any desktopManager enabled default.
 
diff --git a/nixos/doc/manual/release-notes/rl-2003.section.md b/nixos/doc/manual/release-notes/rl-2003.section.md
index 76cee8858e80a..695f8a2c95cab 100644
--- a/nixos/doc/manual/release-notes/rl-2003.section.md
+++ b/nixos/doc/manual/release-notes/rl-2003.section.md
@@ -482,7 +482,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   - If you use `postgresql` on a different server, you don't need to change anything as well since this module was never designed to configure remote databases.
 
-  - If you use `postgresql` and configured your synapse initially on `19.09` or older, you simply need to enable postgresql-support explicitly:
+  - If you use `postgresql` and configured your synapse initially on `19.09` or older, you need to enable postgresql-support explicitly:
 
     ```nix
     { ... }: {
diff --git a/nixos/doc/manual/release-notes/rl-2009.section.md b/nixos/doc/manual/release-notes/rl-2009.section.md
index 6bb75a04b3e8a..eac02a8ff445b 100644
--- a/nixos/doc/manual/release-notes/rl-2009.section.md
+++ b/nixos/doc/manual/release-notes/rl-2009.section.md
@@ -422,7 +422,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
 - The `systemd-networkd` option `systemd.network.networks._name_.dhcpConfig` has been renamed to [systemd.network.networks._name_.dhcpV4Config](options.html#opt-systemd.network.networks._name_.dhcpV4Config) following upstream systemd's documentation change. See systemd.network 5 for details.
 
-- In the `picom` module, several options that accepted floating point numbers encoded as strings (for example [services.picom.activeOpacity](options.html#opt-services.picom.activeOpacity)) have been changed to the (relatively) new native `float` type. To migrate your configuration simply remove the quotes around the numbers.
+- In the `picom` module, several options that accepted floating point numbers encoded as strings (for example [services.picom.activeOpacity](options.html#opt-services.picom.activeOpacity)) have been changed to the (relatively) new native `float` type. To migrate your configuration remove the quotes around the numbers.
 
 - When using `buildBazelPackage` from Nixpkgs, `flat` hash mode is now used for dependencies instead of `recursive`. This is to better allow using hashed mirrors where needed. As a result, these hashes will have changed.
 
diff --git a/nixos/doc/manual/release-notes/rl-2105.section.md b/nixos/doc/manual/release-notes/rl-2105.section.md
index 080ca68d92581..cae3f8a850115 100644
--- a/nixos/doc/manual/release-notes/rl-2105.section.md
+++ b/nixos/doc/manual/release-notes/rl-2105.section.md
@@ -353,7 +353,7 @@ When upgrading from a previous release, please be aware of the following incompa
 
   Another benefit of the refactoring is that we can now issue reloads via either `pkill -HUP unbound` and `systemctl reload unbound` to reload the running configuration without taking the daemon offline. A prerequisite of this was that unbound configuration is available on a well known path on the file system. We are using the path `/etc/unbound/unbound.conf` as that is the default in the CLI tooling which in turn enables us to use `unbound-control` without passing a custom configuration location.
 
-  The module has also been reworked to be [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) compliant. As such, `sevices.unbound.extraConfig` has been removed and replaced by [services.unbound.settings](options.html#opt-services.unbound.settings). `services.unbound.interfaces` has been renamed to `services.unbound.settings.server.interface`.
+  The module has also been reworked to be [RFC 0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) compliant. As such, `services.unbound.extraConfig` has been removed and replaced by [services.unbound.settings](options.html#opt-services.unbound.settings). `services.unbound.interfaces` has been renamed to `services.unbound.settings.server.interface`.
 
   `services.unbound.forwardAddresses` and `services.unbound.allowedAccess` have also been changed to use the new settings interface. You can follow the instructions when executing `nixos-rebuild` to upgrade your configuration to use the new interface.
 
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 400eb1062d9a7..8edf4fd35e4fb 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -100,7 +100,7 @@ In addition to numerous new and upgraded packages, this release has the followin
 - [opensnitch](https://github.com/evilsocket/opensnitch), an application firewall. Available as [services.opensnitch](#opt-services.opensnitch.enable).
 
 - [snapraid](https://www.snapraid.it/), a backup program for disk arrays.
-  Available as [snapraid](#opt-snapraid.enable).
+  Available as [snapraid](#opt-services.snapraid.enable).
 
 - [Hockeypuck](https://github.com/hockeypuck/hockeypuck), a OpenPGP Key Server. Available as [services.hockeypuck](#opt-services.hockeypuck.enable).
 
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index d4581fe9441c8..6f5a807f478a2 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -935,8 +935,7 @@ In addition to numerous new and upgraded packages, this release has the followin
   using the `pomerium-cli` command, you should now install the `pomerium-cli`
   package.
 
-- The option
-  [services.networking.networkmanager.enableFccUnlock](#opt-networking.networkmanager.enableFccUnlock)
+- The option `services.networking.networkmanager.enableFccUnlock`
   was added to support FCC unlock procedures. Since release 1.18.4, the ModemManager
   daemon no longer automatically performs the FCC unlock procedure by default. See
   [the docs](https://modemmanager.org/docs/modemmanager/fcc-unlock/) for more details.
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 97a305573501c..1c73d0c9790d5 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -14,7 +14,7 @@ In addition to numerous new and upgraded packages, this release includes the fol
   - 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`.
+    - On the other hand, for interactively configured user passwords, 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.
@@ -130,7 +130,7 @@ In addition to numerous new and upgraded packages, this release includes the fol
   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`.
+  `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
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index c9da29063e1a6..21c798b3b4a46 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -87,7 +87,7 @@ In addition to numerous new and updated packages, this release has the following
 
 - [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer.  Available as [services.gmediarender](options.html#opt-services.gmediarender.enable).
 
-- [go2rtc](https://github.com/AlexxIT/go2rtc), a camera streaming appliation with support for RTSP, WebRTC, HomeKit, FFMPEG, RTMP and other protocols. Available as [services.go2rtc](options.html#opt-services.go2rtc.enable).
+- [go2rtc](https://github.com/AlexxIT/go2rtc), a camera streaming application with support for RTSP, WebRTC, HomeKit, FFMPEG, RTMP and other protocols. Available as [services.go2rtc](options.html#opt-services.go2rtc.enable).
 
 - [goeland](https://github.com/slurdge/goeland), an alternative to rss2email written in Golang with many filters. Available as [services.goeland](#opt-services.goeland.enable).
 
@@ -203,7 +203,7 @@ In addition to numerous new and updated packages, this release has the following
 
 - `graylog` has been updated to version 5, which can not be updated directly from the previously packaged version 3.3. If you had installed the previously packaged version 3.3, please follow the [upgrade path](https://go2docs.graylog.org/5-0/upgrading_graylog/upgrade_path.htm) from 3.3 to 4.0 to 4.3 to 5.0.
 
-- `buildFHSUserEnv` is now called `buildFHSEnv` and uses FlatPak's Bubblewrap sandboxing tool rather than Nixpkgs' own chrootenv. The old chrootenv-based implemenation is still available via `buildFHSEnvChroot` but is considered deprecated and will be removed when the remaining uses inside Nixpkgs have been migrated. If your FHSEnv-wrapped application misbehaves when using the new bubblewrap implementation, please create an issue in Nixpkgs.
+- `buildFHSUserEnv` is now called `buildFHSEnv` and uses FlatPak's Bubblewrap sandboxing tool rather than Nixpkgs' own chrootenv. The old chrootenv-based implementation is still available via `buildFHSEnvChroot` but is considered deprecated and will be removed when the remaining uses inside Nixpkgs have been migrated. If your FHSEnv-wrapped application misbehaves when using the new bubblewrap implementation, please create an issue in Nixpkgs.
 
 - `nushell` has been updated to at least version 0.77.0, which includes potential breaking changes in aliases. The old aliases are now available as `old-alias` but it is recommended you migrate to the new format. See [Reworked aliases](https://www.nushell.sh/blog/2023-03-14-nushell_0_77.html#reworked-aliases-breaking-changes-kubouch).
 
@@ -555,7 +555,7 @@ In addition to numerous new and updated packages, this release has the following
 
 - `buildDunePackage` now defaults to `strictDeps = true` which means that any library should go into `buildInputs` or `checkInputs`. Any executable that is run on the building machine should go into `nativeBuildInputs` or `nativeCheckInputs` respectively. Example of executables are `ocaml`, `findlib` and `menhir`. PPXs are libraries which are built by dune and should therefore not go into `nativeBuildInputs`.
 
-- `buildFHSUserEnv` is now called `buildFHSEnv` and uses FlatPak's Bubblewrap sandboxing tool rather than Nixpkgs' own chrootenv. The old chrootenv-based implemenation is still available via `buildFHSEnvChroot` but is considered deprecated and will be removed when the remaining uses inside Nixpkgs have been migrated. If your FHSEnv-wrapped application misbehaves when using the new bubblewrap implementation, please create an issue in Nixpkgs.
+- `buildFHSUserEnv` is now called `buildFHSEnv` and uses FlatPak's Bubblewrap sandboxing tool rather than Nixpkgs' own chrootenv. The old chrootenv-based implementation is still available via `buildFHSEnvChroot` but is considered deprecated and will be removed when the remaining uses inside Nixpkgs have been migrated. If your FHSEnv-wrapped application misbehaves when using the new bubblewrap implementation, please create an issue in Nixpkgs.
 
 - Top-level `buildPlatform`, `hostPlatform`, `targetPlatform` have been deprecated, use `stdenv.X` instead.
 
@@ -611,7 +611,7 @@ If you are:
 - adding new rules with `*.rules`
 - running custom PulseAudio commands with `pulse.cmd`
 
-Simply move the definitions into the drop-in.
+Move the definitions into the drop-in.
 
 Note that the use of `context.exec` is not recommended and other methods of running your thing are likely a better option.
 
@@ -660,5 +660,5 @@ If reloading the module is not an option, proceed to [Nuclear option](#sec-relea
 
 #### Nuclear option {#sec-release-23.05-migration-pipewire-nuclear}
 If all else fails, you can still manually copy the contents of the default configuration file
-from `${pkgs.pipewire.lib}/share/pipewire` to `/etc/pipewire` and edit it to fully override the default.
+from `${pkgs.pipewire}/share/pipewire` to `/etc/pipewire` and edit it to fully override the default.
 However, this should be done only as a last resort. Please talk to the Pipewire maintainers if you ever need to do this.
diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md
index aa61970392443..1aef1828908f8 100644
--- a/nixos/doc/manual/release-notes/rl-2311.section.md
+++ b/nixos/doc/manual/release-notes/rl-2311.section.md
@@ -1,272 +1,1438 @@
-# Release 23.11 (“Tapir”, 2023.11/??) {#sec-release-23.11}
-
-## Highlights {#sec-release-23.11-highlights}
-
-- FoundationDB now defaults to major version 7.
-
-- Support for WiFi6 (IEEE 802.11ax) and WPA3-SAE-PK was enabled in the `hostapd` package, along with a significant rework of the hostapd module.
-
-- LXD now supports virtual machine instances to complement the existing container support
-
-## New Services {#sec-release-23.11-new-services}
-
-- [MCHPRS](https://github.com/MCHPR/MCHPRS), a multithreaded Minecraft server built for redstone. Available as [services.mchprs](#opt-services.mchprs.enable).
-
-- [acme-dns](https://github.com/joohoi/acme-dns), a limited DNS server to handle ACME DNS challenges easily and securely. Available as [services.acme-dns](#opt-services.acme-dns.enable).
-
-<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
-
-- [river](https://github.com/riverwm/river), A dynamic tiling wayland compositor. Available as [programs.river](#opt-programs.river.enable).
-
-- [wayfire](https://wayfire.org), A modular and extensible wayland compositor. Available as [programs.wayfire](#opt-programs.wayfire.enable).
-
-- [mautrix-whatsapp](https://docs.mau.fi/bridges/go/whatsapp/index.html) A Matrix-WhatsApp puppeting bridge
+# Release 23.11 (“Tapir”, 2023.11/29) {#sec-release-23.11}
+
+The NixOS release team is happy to announce a new version of NixOS. The release is called NixOS 23.11 ("Tapir").
+
+NixOS is a Linux distribution, whose set of packages can also be used on other Linux systems and macOS.
+
+Support is planned until the end of June 2024, handing over to NixOS 24.05.
+
+To upgrade to the latest release, follow the upgrade chapter and check the [Breaking Changes](#sec-release-23.11-nixos-breaking-changes)
+section for packages and services used in your configuration.
+
+The team is excited about the many software updates and improvements in this release. Just to name a few, do check the updates
+for `GNOME` packages, `systemd`, `glibc`, the `ROCM` package set, and `hostapd` (which brings support for WiFi6 (IEEE 802.11ax) and WPA3-SAE-PK).
+
+Make sure to also check the many updates in the [Nixpkgs library](#sec-release-23.11-nixpkgs-lib) when developing your own packages.
+
+## Table of Contents {#sec-release-23.11-toc}
+
+- [NixOS](#sec-release-23.11-nixos)
+  - [Breaking Changes](#sec-release-23.11-nixos-breaking-changes)
+  - [New Services](#sec-release-23.11-nixos-new-services)
+  - [Other Notable Changes](#sec-release-23.11-nixos-notable-changes)
+- [Nixpkgs Library](#sec-release-23.11-nixpkgs-lib)
+  - [Breaking Changes](#sec-release-23.11-lib-breaking)
+  - [Additions and Improvements](#sec-release-23.11-lib-additions-improvements)
+  - [Deprecations](#sec-release-23.11-lib-deprecations)
+
+## NixOS {#sec-release-23.11-nixos}
+
+
+### Breaking Changes {#sec-release-23.11-nixos-breaking-changes}
+
+- `services.postgresql.ensurePermissions` has been deprecated in favor of
+  `services.postgresql.ensureUsers.*.ensureDBOwnership` which simplifies the
+  setup of database owned by a certain system user in local database contexts
+  (which make use of peer authentication via UNIX sockets), migration
+  guidelines were provided in the NixOS manual, please refer to them if you are
+  affected by a PostgreSQL 15 changing the way `GRANT ALL PRIVILEGES` is
+  working. `services.postgresql.ensurePermissions` will be removed in 24.05.
+  All NixOS modules were migrated using one of the strategy, e.g.
+  `ensureDBOwnership` or `postStart`. Refer to the [PR
+  #266270](https://github.com/NixOS/nixpkgs/pull/266270) for more details.
+
+- `network-online.target` has been fixed to no longer time out for systems with
+  `networking.useDHCP = true` and `networking.useNetworkd = true`. Workarounds
+  for this can be removed.
+
+- The `boot.loader.raspberryPi` options have been marked deprecated, with
+  intent of removal for NixOS 24.11. They had a limited use-case, and do not
+  work like people expect. They required either very old installs from ([before
+  mid-2019](https://github.com/NixOS/nixpkgs/pull/62462)) or customized builds
+  out of scope of the standard and generic AArch64 support. That option set
+  never supported the Raspberry Pi 4 family of devices.
+
+- `python3.pkgs.sequoia` was removed in favor of `python3.pkgs.pysequoia`. The
+  latter package is based on upstream's dedicated repository for sequoia's
+  Python bindings, where the Python bindings from
+  [gitlab:sequoia-pgp/sequoia](https://gitlab.com/sequoia-pgp/sequoia) were
+  removed long ago.
+
+- `writeTextFile` requires `executable` to be boolean now, values like `null`
+  or `""` will fail to evaluate now.
+
+- The latest version of `clonehero` now stores custom content in
+  `~/.clonehero`. Refer to the [migration
+  instructions](https://clonehero.net/2022/11/29/v23-to-v1-migration-instructions.html)
+  for more details. Typically, these content files would exist along side the
+  binary, but the previous build used a wrapper script that would store them in
+  `~/.config/unity3d/srylain Inc_/Clone Hero`.
+
+- `services.mastodon` doesn't support providing a TCP port to its `streaming`
+  component anymore, as upstream implemented parallelization by running
+  multiple instances instead of running multiple processes in one instance.
+  Please create a PR if you are interested in this feature.\
+  Due to this, the desired number of such instances
+  {option}`services.mastodon.streamingProcesses` now needs to be declared explicitly.
+
+- The `services.hostapd` module was rewritten to support `passwordFile` like
+  options, WPA3-SAE, and management of multiple interfaces. This breaks
+  compatibility with older configurations.
+  - `hostapd` is now started with additional systemd sandbox/hardening options
+    for better security.
+  - `services.hostapd.interface` was replaced with a per-radio and per-bss
+    configuration scheme using
+    [services.hostapd.radios](#opt-services.hostapd.radios).
+  - `services.hostapd.wpa` has been replaced by
+    [services.hostapd.radios.&lt;name&gt;.networks.&lt;name&gt;.authentication.wpaPassword](#opt-services.hostapd.radios._name_.networks._name_.authentication.wpaPassword)
+    and
+    [services.hostapd.radios.&lt;name&gt;.networks.&lt;name&gt;.authentication.saePasswords](#opt-services.hostapd.radios._name_.networks._name_.authentication.saePasswords)
+    which configure WPA2-PSK and WP3-SAE respectively.
+  - The default authentication has been changed to WPA3-SAE. Options for other
+    (legacy) schemes are still available.
+
+- `python3.pkgs.fetchPypi` and `python3Packages.fetchPypi` have been
+  deprecated in favor of top-level `fetchPypi`.
+
+- xdg-desktop-portal has been updated to 1.18, which reworked how portal
+  implementations are selected. If you roll your own desktop environment, you
+  should either set `xdg.portal.config` or `xdg.portal.configPackages`, which
+  allow fine-grained control over which portal backend to use for specific
+  interfaces, as described in {manpage}`portals.conf(5)`.
+
+  If you don't provide configurations, a portal backend will only be considered
+  when the desktop you use matches its deprecated `UseIn` key. While some NixOS
+  desktop modules should already ship one for you, it is suggested to test
+  portal availability by trying [Door
+  Knocker](https://flathub.org/apps/xyz.tytanium.DoorKnocker) and [ASHPD
+  Demo](https://flathub.org/apps/com.belmoussaoui.ashpd.demo). If things
+  regressed, you may run `G_MESSAGES_DEBUG=all
+  /path/to/xdg-desktop-portal/libexec/xdg-desktop-portal` for ideas on which
+  config file and which portals are chosen.
+
+- `pass` now does not contain `password-store.el`. Users should get
+  `password-store.el` from Emacs lisp package set `emacs.pkgs.password-store`.
+
+- `services.knot` now supports `.settings` from RFC42.  The previous
+  `.extraConfig` still works the same, but it displays a warning now.
+
+- `services.invoiceplane` now supports `.settings` from RFC42. The previous
+  `.extraConfig` still works the same way, but it displays a warning now.
+
+- `mu` does not install `mu4e` files by default now. Users should get `mu4e`
+  from Emacs lisp package set `emacs.pkgs.mu4e`.
+
+- `mariadb` now defaults to `mariadb_1011` instead of `mariadb_106`, meaning
+  the default version was upgraded from v10.6.x to v10.11.x. Refer to the
+  [upgrade
+  notes](https://mariadb.com/kb/en/upgrading-from-mariadb-10-6-to-mariadb-10-11/)
+  for potential issues.
+
+- `getent` has been moved from `glibc`'s `bin` output to its own dedicated
+  output, reducing closure size for many dependents. Dependents using the
+  `getent` alias should not be affected; others should move from using
+  `glibc.bin` or `getBin glibc` to `getent` (which also improves compatibility
+  with non-glibc platforms).
+
+- `maintainers/scripts/update-luarocks-packages` is now a proper package
+  `luarocks-packages-updater` that can be run to maintain out-of-tree luarocks
+  packages.
+
+- The `users.users.<name>.passwordFile` has been renamed to
+  `users.users.<name>.hashedPasswordFile` to avoid possible confusions. The
+  option is in fact the file-based version of `hashedPassword`, not `password`,
+  and expects a file containing the {manpage}`crypt(3)` hash of the user
+  password.
+
+- `chromiumBeta` and `chromiumDev` have been removed due to the lack of
+  maintenance in nixpkgs. Consider using `chromium` instead.
+
+- `google-chrome-beta` and `google-chrome-dev` have been removed due to the
+  lack of maintenance in nixpkgs. Consider using `google-chrome` instead.
+
+- The `services.ananicy.extraRules` option now has the type of `listOf attrs`
+  instead of `string`.
+
+- `buildVimPluginFrom2Nix` has been renamed to `buildVimPlugin`, which now
+  now skips `configurePhase` and `buildPhase`.
+
+- JACK tools (`jack_*` except `jack_control`) have moved from the `jack2`
+  package to `jack-example-tools`.
+
+- The `waagent` service does provisioning now.
+
+- The `matrix-synapse` package & module have undergone some significant
+  internal changes, for most setups no intervention is needed, though:
+  - The option
+    [`services.matrix-synapse.package`](#opt-services.matrix-synapse.package)
+    is read-only now. For modifying the package, use an overlay which modifies
+    `matrix-synapse-unwrapped` instead. More on that below.
+  - The `enableSystemd` & `enableRedis` arguments have been removed and
+    `matrix-synapse` has been renamed to `matrix-synapse-unwrapped`. Also,
+    several optional dependencies (such as `psycopg2` or `authlib`) have been
+    removed.
+  - These optional dependencies are automatically added via a wrapper
+    (`pkgs.matrix-synapse.override { extras = ["redis"]; }` for `hiredis` &
+    `txredisapi` for instance) if the relevant config section is declared in
+    `services.matrix-synapse.settings`. For instance, if
+    `services.matrix-synapse.settings.redis.enabled` is set to `true`,
+    `"redis"` will be automatically added to the `extras` list of
+    `pkgs.matrix-synapse`.
+  - A list of all extras (and the extras enabled by default) can be found at
+    the [option's reference for
+    `services.matrix-synapse.extras`](#opt-services.matrix-synapse.extras).
+  - In some cases (e.g. for running synapse workers) it was necessary to re-use
+    the `PYTHONPATH` of `matrix-synapse.service`'s environment to have all
+    plugins available. This isn't necessary anymore, instead
+    `config.services.matrix-synapse.package` can be used as it points to the
+    wrapper with properly configured `extras` and also all plugins defined via
+    [`services.matrix-synapse.plugins`](#opt-services.matrix-synapse.plugins)
+    available. This is also the reason for why the option is read-only now,
+    it's supposed to be set by the module only.
+
+- `netbox` was updated to v3.6. `services.netbox.package` still defaults
+  to v3.5 if `stateVersion` is earlier than 23.11. Refer to upstream's breaking
+  changes [for
+  v3.6.0](https://github.com/netbox-community/netbox/releases/tag/v3.6.0) and
+  upgrade NetBox by changing `services.netbox.package`. Database migrations
+  will be run automatically.
+
+- `etcd` has been updated to v3.5. Refer to upgrade guides for [v3.3 to
+  v3.4](https://etcd.io/docs/v3.5/upgrades/upgrade_3_4/) and [v3.4 to
+  v3.5](https://etcd.io/docs/v3.5/upgrades/upgrade_3_5/) for more details.
+
+- `gitlab` installations created or updated between versions \[15.11.0,
+  15.11.2] have an incorrect database schema. This will become a problem when
+  upgrading to `gitlab` >=16.2.0. A workaround for affected users can be found
+  in the [GitLab
+  docs](https://docs.gitlab.com/ee/update/versions/gitlab_16_changes.html#undefined-column-error-upgrading-to-162-or-later).
+
+
+- `consul` has been updated to v1.16.0. Refer to the [release
+  note](https://github.com/hashicorp/consul/releases/tag/v1.16.0) for more
+  details. Once a new Consul version has started and upgraded it's data
+  directory, it generally cannot be downgraded to the previous version.
+
+- `llvmPackages_rocm` has been moved to `rocmPackages.llvm`.
+
+- `hip`, `rocm-opencl-runtime`, `rocm-opencl-icd`, and `rocclr` have been
+  combined into `rocmPackages.clr`.
+
+- `clang-ocl`, `clr`, `composable_kernel`, `hipblas`, `hipcc`, `hip-common`, `hipcub`,
+  `hipfft`, `hipfort`, `hipify`, `hipsolver`, `hipsparse`, `migraphx`, `miopen`, `miopengemm`,
+  `rccl`, `rdc`, `rocalution`, `rocblas`, `rocdgbapi`, `rocfft`, `rocgdb`, `rocm-cmake`,
+  `rocm-comgr`, `rocm-core`, `rocm-device-libs`, `rocminfo`, `rocmlir`, `rocm-runtime`,
+  `rocm-smi`, `rocm-thunk`, `rocprim`, `rocprofiler`, `rocrand`, `rocr-debug-agent`,
+  `rocsolver`, `rocsparse`, `rocthrust`, `roctracer`, `rocwmma`, and `tensile`
+  have been moved to `rocmPackages`.
+
+- `himalaya` has been updated to v0.8.0, which drops the native TLS support
+  (in favor of Rustls) and add OAuth 2.0 support. Refer to the [release
+  note](https://github.com/soywod/himalaya/releases/tag/v0.8.0) for more
+  details.
+
+
+- `nix-prefetch-git` now ignores global and user git config, to improve
+  reproducibility.
+
+- The [services.caddy.acmeCA](#opt-services.caddy.acmeCA) option defaults
+  to `null` instead of `"https://acme-v02.api.letsencrypt.org/directory"` now.
+  To use all of Caddy's default ACME CAs and enable Caddy's automatic issuer
+  fallback feature by default, as recommended by upstream.
+
+- The default priorities of
+  [`services.nextcloud.phpOptions`](#opt-services.nextcloud.phpOptions) have
+  changed. This means that e.g.
+  `services.nextcloud.phpOptions."opcache.interned_strings_buffer" = "23";`
+  doesn't discard all of the other defaults from this option anymore. The
+  attribute values of `phpOptions` are still defaults, these can be overridden
+  as shown here.
+
+  To override all of the options (including including `upload_max_filesize`,
+  `post_max_size` and `memory_limit` which all point to
+  [`services.nextcloud.maxUploadSize`](#opt-services.nextcloud.maxUploadSize)
+  by default) can be done like this:
 
-- [hddfancontrol](https://github.com/desbma/hddfancontrol), a service to regulate fan speeds based on hard drive temperature. Available as [services.hddfancontrol](#opt-services.hddfancontrol.enable).
+  ```nix
+  {
+    services.nextcloud.phpOptions = lib.mkForce {
+      /* ... */
+    };
+  }
+  ```
 
-- [GoToSocial](https://gotosocial.org/), an ActivityPub social network server, written in Golang. Available as [services.gotosocial](#opt-services.gotosocial.enable).
+- `php80` is no longer supported due to upstream not supporting this version
+  anymore.
 
-- [Typesense](https://github.com/typesense/typesense), a fast, typo-tolerant search engine for building delightful search experiences. Available as [services.typesense](#opt-services.typesense.enable).
+- PHP defaults to PHP 8.2 now, updated from v8.1.
 
-* [NS-USBLoader](https://github.com/developersu/ns-usbloader/), an all-in-one tool for managing Nintendo Switch homebrew. Available as [programs.ns-usbloader](#opt-programs.ns-usbloader.enable).
+- GraalVM has been updated to the latest version, and this brings significant
+  changes. Upstream don't release multiple versions targeting different JVMs
+  anymore, so now we only have one GraalVM derivation (`graalvm-ce`). While at
+  first glance the version may seem a downgrade (v22.3.1 -> v21.0.0), the major
+  version is now following the JVM it targets (so this latest version targets
+  JVM 21). Also some products like `llvm-installable-svm` and
+  `native-image-svm` were incorporate to the main GraalVM derivation, so
+  they're included by default.
 
-- [Anuko Time Tracker](https://github.com/anuko/timetracker), a simple, easy to use, open source time tracking system. Available as [services.anuko-time-tracker](#opt-services.anuko-time-tracker.enable).
+- GraalPy (`graalCEPackages.graalpy`), TruffleRuby
+  (`graalCEPackages.truffleruby`), GraalJS (`graalCEPackages.graaljs`) and
+  GraalNodeJS (`grallCEPackages.graalnodejs`) are now independent from the main
+  GraalVM derivation.
 
-- [Prometheus MySQL exporter](https://github.com/prometheus/mysqld_exporter), a MySQL server exporter for Prometheus. Available as [services.prometheus.exporters.mysqld](#opt-services.prometheus.exporters.mysqld.enable).
+- The ISC DHCP package and corresponding module have been removed, because they
+  are EOL upstream. Refer [to this
+  post](https://www.isc.org/blogs/isc-dhcp-eol/) for details and switch to a
+  different DHCP implementation like kea or dnsmasq.
 
-- [sitespeed-io](https://sitespeed.io), a tool that can generate metrics (timings, diagnostics) for websites. Available as [services.sitespeed-io](#opt-services.sitespeed-io.enable).
+- `prometheus-unbound-exporter` has been replaced by the Let's Encrypt
+  maintained version, since the previous version was archived. This requires
+  some changes to the module configuration, most notable `controlInterface`
+  needs migration towards `unbound.host` and requires either the `tcp://` or
+  `unix://` URI scheme.
 
-- [stalwart-mail](https://stalw.art), an all-in-one email server (SMTP, IMAP, JMAP). Available as [services.stalwart-mail](#opt-services.stalwart-mail.enable).
+- `odoo` defaults to v16 now, updated from v15.
 
-- [Jool](https://nicmx.github.io/Jool/en/index.html), an Open Source implementation of IPv4/IPv6 translation on Linux. Available as [networking.jool.enable](#opt-networking.jool.enable).
+- `varnish` was upgraded from v7.2.x to v7.4.x. Refer to upgrade guides vor
+  [v7.3](https://varnish-cache.org/docs/7.3/whats-new/upgrading-7.3.html) and
+  [v7.4](https://varnish-cache.org/docs/7.4/whats-new/upgrading-7.4.html). The
+  current LTS version is still offered as `varnish60`.
 
-- [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services.
+- `util-linux` is now supported on Darwin and is no longer an alias to
+  `unixtools`. Use the `unixtools.util-linux` package for access to the Apple
+  variants of the utilities.
 
-- [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler. Available as [services.pgbouncer](#opt-services.pgbouncer.enable).
+- `services.keyd` changed API. Now you can create multiple configuration files.
 
-- [trust-dns](https://trust-dns.org/), a Rust based DNS server built to be safe and secure from the ground up. Available as [services.trust-dns](#opt-services.trust-dns.enable).
+- `baloo`, the file indexer and search engine used by KDE now has a patch to
+  prevent files from constantly being reindexed when the device IDs of the
+  their underlying storage change. This happens frequently when using btrfs or
+  LVM. The patch has not yet been accepted upstream but it provides a
+  significantly improved experience. When upgrading, reset baloo to get a clean
+  index: `balooctl disable ; balooctl purge ; balooctl enable`.
+
+- The `vlock` program from the `kbd` package has been moved into its own
+  package output and should now be referenced explicitly as `kbd.vlock` or
+  replaced with an alternative such as the standalone `vlock` package or
+  `physlock`.
+
+- `fileSystems.<name>.autoFormat` now uses `systemd-makefs`, which does not
+  accept formatting options. Therefore, `fileSystems.<name>.formatOptions` has
+  been removed.
+
+- `fileSystems.<name>.autoResize` uses `systemd-growfs` to resize the file
+  system online in Stage 2 now. This means that `f2fs` and `ext2` can no longer
+  be auto resized, while `xfs` and `btrfs` now can be.
+
+- `fuse3` has been updated from v3.11.0 to v3.16.2. Refer to the
+  [changelog](https://github.com/libfuse/libfuse/blob/fuse-3.16.2/ChangeLog.rst#libfuse-3162-2023-10-10)
+  for an overview of the changes.
+
+  Unsupported mount options are no longer silently accepted [(since
+  3.15.0)](https://github.com/libfuse/libfuse/blob/fuse-3.16.2/ChangeLog.rst#libfuse-3150-2023-06-09).
+  The [affected mount
+  options](https://github.com/libfuse/libfuse/commit/dba6b3983af34f30de01cf532dff0b66f0ed6045)
+  are: `atime`, `diratime`, `lazytime`, `nolazytime`, `relatime`, `norelatime`,
+  `strictatime`.
+
+  For example,
+
+  ```bash
+  $ sshfs 127.0.0.1:/home/test/testdir /home/test/sshfs_mnt -o atime
+  ```
 
-- [osquery](https://www.osquery.io/), a SQL powered operating system instrumentation, monitoring, and analytics.
+  would previously terminate successfully with the mount point established, now
+  it outputs the error message ``fuse: unknown option(s): `-o atime'`` and
+  terminates with exit status 1.
+
+- `nixos-rebuild {switch,boot,test,dry-activate}` runs the system
+  activation inside `systemd-run` now, creating an ephemeral systemd service
+  and protecting the system switch against issues like network disconnections
+  during remote (e.g. SSH) sessions. This has the side effect of running the
+  switch in an isolated environment, that could possible break post-switch
+  scripts that depends on things like environment variables being set. If you
+  want to opt-out from this behavior for now, you may set the
+  `NIXOS_SWITCH_USE_DIRTY_ENV` environment variable before running
+  `nixos-rebuild`. However, keep in mind that this option will be removed in
+  the future.
+
+- The `services.vaultwarden.config` option default value was changed to make
+  Vaultwarden only listen on localhost, following the [secure defaults for most
+  NixOS services](https://github.com/NixOS/nixpkgs/issues/100192).
+
+- `services.lemmy.settings.federation` was removed in v0.17.0 and no longer has
+  any effect. To enable federation, the hostname must be set in the
+  configuration file and then federation must be enabled in the admin web UI.
+  Refer to the [release
+  notes](https://github.com/LemmyNet/lemmy/blob/c32585b03429f0f76d1e4ff738786321a0a9df98/RELEASES.md#upgrade-instructions)
+  for more details.
+
+- `pict-rs` was upgraded from v0.3 to v0.4 and contains an incompatible database
+  & configuration change. To upgrade on systems with `stateVersion = "23.05";`
+  or older follow the migration steps from
+  https://git.asonix.dog/asonix/pict-rs#user-content-0-3-to-0-4-migration-guide
+  and set `services.pict-rs.package = pkgs.pict-rs;`.
+
+- The following packages in `haskellPackages` have a separate bin output now:
+  `cabal-fmt`, `calligraphy`, `eventlog2html`, `ghc-debug-brick`, `hindent`,
+  `nixfmt`, `releaser`. This means you need to replace e.g.
+  `"${pkgs.haskellPackages.nixfmt}/bin/nixfmt"` with `"${lib.getBin
+  pkgs.haskellPackages.nixfmt}/bin/nixfmt"` or `"${lib.getExe
+  pkgs.haskellPackages.nixfmt}"`. The binaries also won’t be in scope if you
+  rely on them being installed e.g. via `ghcWithPackages`.
+  `environment.packages` picks the `bin` output automatically, so for normal
+  installation no intervention is required. Also, toplevel attributes like
+  `pkgs.nixfmt` are not impacted negatively by this change.
+
+- `spamassassin` no longer supports the `Hashcash` module. The module needs to
+  be removed from the `loadplugin` list if it was copied over from the default
+  `initPreConf` option.
+
+- `nano` was removed from `environment.defaultPackages`. To not leave systems
+  without a editor, now `programs.nano.enable` is enabled by default.
+
+- `programs.nano.nanorc` and `programs.nano.syntaxHighlight` no longer have an
+  effect unless `programs.nano.enable` is set to true which is the default.
+
+- `services.outline.sequelizeArguments` has been removed, as `outline` no
+  longer executes database migrations via the `sequelize` cli.
+
+- The binary of the package `cloud-sql-proxy` has changed from
+  `cloud_sql_proxy` to `cloud-sql-proxy`.
+
+- The module `services.apache-kafka` was largely rewritten and has certain
+  breaking changes. To be precise, this means that the following things have
+  changed:
+  - Most settings have been migrated to
+    [services.apache-kafka.settings](#opt-services.apache-kafka.settings).
+    - Care must be taken when adapting an existing cluster to these changes,
+      see [](#module-services-apache-kafka-migrating-to-settings).
+  - By virtue of being less opinionated, it is now possible to use the module
+    to run Apache Kafka in KRaft mode instead of Zookeeper mode.
+    - [A few options](#module-services-apache-kafka-kraft) have been added to
+      assist in this mode.
+
+- Garage has been upgraded to v0.9.x. `services.garage.package` needs to be
+  explicitly set now, so version upgrades can be done in a controlled fashion.
+  For this, we expose `garage_x_y` attributes which can be set here.
+
+- `voms` and `xrootd` now moves the `$out/etc` content to the `$etc` output
+  instead of `$out/etc.orig`, when input argument `externalEtc` is not `null`.
+
+- The `woodpecker-*` CI packages have been updated to v1.0.0. This release is
+  wildly incompatible with the v0.15.x versions that were previously packaged.
+  Refer to [upstream's
+  documentation](https://woodpecker-ci.org/docs/next/migrations#100) to learn
+  how to update your CI configurations.
+
+- Meilisearch was updated from v1.3.1 to v1.5.0. The update has breaking
+  changes about backslashes and filtering. Refer to the [release
+  announcement](https://blog.meilisearch.com/v1-4-release/) for more
+  details.
+
+- The Caddy module gained a new option named `services.caddy.enableReload`
+  which is enabled by default. It allows reloading the service instead of
+  restarting it, if only a config file has changed. This option must be
+  disabled if you have turned off the [Caddy admin
+  API](https://caddyserver.com/docs/caddyfile/options#admin). If you keep this
+  option enabled, you should consider setting
+  [`grace_period`](https://caddyserver.com/docs/caddyfile/options#grace-period)
+  to a non-infinite value to prevent Caddy from delaying the reload
+  indefinitely.
+
+- mdraid support is optional now. This reduces initramfs size and prevents the
+  potentially undesired automatic detection and activation of software RAID
+  pools. It is disabled by default in new configurations (determined by
+  `stateVersion`), but the appropriate settings will be generated by
+  `nixos-generate-config` when installing to a software RAID device, so the
+  standard installation procedure should be unaffected. If you have custom
+  configs relying on mdraid, ensure that you use `stateVersion` correctly or
+  set `boot.swraid.enable` manually. On systems with an updated `stateVersion`
+  we now also emit warnings if `mdadm.conf` does not contain the minimum
+  required configuration necessary to run the dynamically enabled monitoring
+  daemons.
+
+- The `go-ethereum` package has been updated to v1.12.0. This drops support for
+  proof-of-work. Its GraphQL API now encodes all numeric values as hex strings
+  and the GraphQL UI is updated to v2.0. The default database has changed from
+  `leveldb` to `pebble` but `leveldb` can be forced with the
+  --db.engine=leveldb flag. The `checkpoint-admin` command was [removed along
+  with trusted
+  checkpoints](https://github.com/ethereum/go-ethereum/pull/27147).
+
+- The `aseprite-unfree` package has been upgraded from v1.2.16.3 to v1.2.40.
+  The free version of aseprite has been dropped because it is EOL and the
+  package attribute now points to the unfree version. A maintained fork of the
+  last free version of Aseprite, named 'LibreSprite', is available in the
+  `libresprite` package.
+
+- The default `kops` version is v1.28.0 now and support for v1.25 and older have
+  been dropped.
+
+- `pharo` has been updated to latest stable v10.0.8, which is compatible with
+  the latest stable and oldstable images (Pharo 10 and 11). The VM in question
+  is the 64bit Spur. The 32bit version has been dropped due to lack of
+  maintenance. The Cog VM has been deleted because it is severily outdated.
+  Finally, the `pharo-launcher` package has been deleted because it was not
+  compatible with the newer VM, and due to lack of maintenance.
+
+- Emacs mainline v29 was introduced. This new version includes many major
+  additions, most notably `tree-sitter` support (enabled by default) and
+  the pgtk variant (useful for Wayland users), which is available under the
+  attribute `emacs29-pgtk`.
 
-- [ebusd](https://ebusd.eu), a daemon for handling communication with eBUS devices connected to a 2-wire bus system (“energy bus” used by numerous heating systems). Available as [services.ebusd](#opt-services.ebusd.enable).
+- Emacs macport version 29 was introduced.
 
-- [systemd-sysupdate](https://www.freedesktop.org/software/systemd/man/systemd-sysupdate.html), atomically updates the host OS, container images, portable service images or other sources. Available as [systemd.sysupdate](opt-systemd.sysupdate).
+- The option `services.networking.networkmanager.enableFccUnlock` was removed
+  in favor of `networking.networkmanager.fccUnlockScripts`, which allows
+  specifying unlock scripts explicitly. The previous option enabled all unlock
+  scripts bundled with ModemManager, which is risky, and didn't allow using
+  vendor-provided unlock scripts at all.
+
+- The `html-proofer` package has been updated from major version 3 to major
+  version 5, which includes [breaking
+  changes](https://github.com/gjtorikian/html-proofer/blob/v5.0.8/UPGRADING.md).
+
+- `kratos` has been updated from v0.10.1 to the first stable v1.0.0, please
+  read the [v0.10.1 to
+  v0.11.0](https://github.com/ory/kratos/releases/tag/v0.11.0), [v0.11.0 to
+  v0.11.1](https://github.com/ory/kratos/releases/tag/v0.11.1), [v0.11.1 to
+  v0.13.0](https://github.com/ory/kratos/releases/tag/v0.13.0) and [v0.13.0 to
+  v1.0.0](https://github.com/ory/kratos/releases/tag/v1.0.0) upgrade guides.
+  The most notable breaking change is the introduction of one-time passwords
+  (`code`) and update of the default recovery strategy from `link` to `code`.
+
+- The `hail` module was removed, as `hail` was unmaintained since 2017.
+
+- Package `noto-fonts-emoji` was renamed to `noto-fonts-color-emoji`. Refer to
+  [PR #221181](https://github.com/NixOS/nixpkgs/issues/221181) for more
+  details.
+
+- Package `cloud-sql-proxy` was renamed to `google-cloud-sql-proxy` as it
+  cannot be used with other cloud providers.
+
+- Package `pash` was removed due to being archived upstream. Use `powershell`
+  as an alternative.
+
+- The option `services.plausible.releaseCookiePath` has been removed. Plausible
+  does not use any distributed Erlang features, and does not plan to (refer to
+  [discussion](https://github.com/NixOS/nixpkgs/pull/130297#issuecomment-1805851333)),
+  Thus NixOS disables them now , and the Erlang cookie becomes unnecessary. You
+  may delete the file that `releaseCookiePath` was set to.
+
+- `security.sudo.extraRules` includes `root`'s default rule now, with ordering
+  priority 400. This is functionally identical for users not specifying rule
+  order, or relying on `mkBefore` and `mkAfter`, but may impact users calling
+  `mkOrder n` with n ≤ 400.
+
+- X keyboard extension (XKB) options have been reorganized into a single
+  attribute set, `services.xserver.xkb`. Specifically,
+  `services.xserver.layout` is `services.xserver.xkb.layout` now,
+  `services.xserver.extraLayouts` is `services.xserver.xkb.extraLayouts` now,
+  `services.xserver.xkbModel` is `services.xserver.xkb.model` now,
+  `services.xserver.xkbOptions` is `services.xserver.xkb.options` now ,
+  `services.xserver.xkbVariant` is `services.xserver.xkb.variant` now, and
+  `services.xserver.xkbDir` is `services.xserver.xkb.dir` now.
+
+- `networking.networkmanager.firewallBackend` was removed as NixOS is now using
+  iptables-nftables-compat even when using iptables, therefore Networkmanager
+  uses the nftables backend unconditionally now.
+
+- `rome` was removed because it is no longer maintained and is succeeded by
+  `biome`.
+
+- The `prometheus-knot-exporter` was migrated to a version maintained by
+  CZ.NIC. Various metric names have changed, so checking existing rules is
+  recommended.
+
+- The `services.mtr-exporter.target` has been removed in favor of
+  `services.mtr-exporter.jobs` which allows specifying multiple targets.
+
+- `blender-with-packages` has been deprecated in favor of
+  `blender.withPackages`, for example `blender.withPackages (ps: [ps.bpycv])`.
+  It behaves similarly to `python3.withPackages`.
+
+- Setting `nixpkgs.config` options while providing an external `pkgs` instance
+  will now raise an error instead of silently ignoring the options. NixOS
+  modules no longer set `nixpkgs.config` to accommodate this. This specifically
+  affects `services.locate`,
+  `services.xserver.displayManager.lightdm.greeters.tiny` and
+  `programs.firefox` NixOS modules. No manual intervention should be required
+  in most cases, however, configurations relying on those modules affecting
+  packages outside the system environment should switch to explicit overlays.
+
+- `privacyidea` (and the corresponding `privacyidea-ldap-proxy`) has been
+  removed from nixpkgs because it has severely outdated dependencies that
+  became unmaintainable with nixpkgs' python package-set.
+
+- `dagger` was removed because using a package called `dagger` and packaging it
+  from source violates their trademark policy.
+
+- `win-virtio` package was renamed to `virtio-win` to be consistent with the upstream package name.
+
+- `ps3netsrv` has been replaced with the webman-mod fork, the executable has
+  been renamed from `ps3netsrv++` to `ps3netsrv` and cli parameters have
+  changed.
+
+- `ssm-agent` package and module were renamed to `amazon-ssm-agent` to be
+  consistent with the upstream package name.
+
+- `services.kea.{ctrl-agent,dhcp-ddns,dhcp,dhcp6}` now use separate runtime
+  directories instead of `/run/kea` to work around the runtime directory being
+  cleared on service start.
+
+- `mkDerivation` rejects MD5 hashes now.
+
+- The `junicode` font package has been updated to [major
+  v2](https://github.com/psb1558/Junicode-font/releases/tag/v2.001), which is
+  a font family now. In particular, plain `Junicode.ttf` no longer exists. In
+  addition, TrueType font files are now placed in `font/truetype` instead of
+  `font/junicode-ttf`; this change does not affect use via `fonts.packages`
+  option.
+
+- The `prayer` package as well as `services.prayer` have been removed because
+  it's been unmaintained for several years and the author's website has
+  vanished.
+
+- The `chrony` NixOS module now tracks the real-time clock drift from the
+  system clock with `rtcfile` and automatically adjusts it with `rtcautotrim`
+  when it exceeds the maximum error specified in
+  `services.chrony.autotrimThreshold` (defaults to 30 seconds). If you enabled
+  `rtcsync` in `extraConfig`, you should remove RTC related options from
+  `extraConfig`. If you do not want chrony configured to keep the RTC in check,
+  you can set `services.chrony.enableRTCTrimming = false;`.
 
-- [eris-server](https://codeberg.org/eris/eris-go). [ERIS](https://eris.codeberg.page/) is an encoding for immutable storage and this server provides block exchange as well as content decoding over HTTP and through a FUSE file-system. Available as [services.eris-server](#opt-services.eris-server.enable).
+- `trilium-desktop` and `trilium-server` have been updated to
+  [v0.61](https://github.com/zadam/trilium/releases/tag/v0.61.12). For existing
+  installations, upgrading to this version is supported only after running
+  v0.60.x at least once. If you are still on an older version, make sure to
+  update to v0.60 (available in NixOS 23.05) first and only then to v0.61
+  (available in NixOS 23.11).
+
+- Cassandra now defaults to v4.x, updated from v3.11.x.
 
-- [Honk](https://humungus.tedunangst.com/r/honk), a complete ActivityPub server with minimal setup and support costs.
-  Available as [services.honk](#opt-services.honk.enable).
 
-- [NNCP](http://www.nncpgo.org/). Added nncp-daemon and nncp-caller services. Configuration is set with [programs.nncp.settings](#opt-programs.nncp.settings) and the daemons are enabled at [services.nncp](#opt-services.nncp.caller.enable).
+- FoundationDB now defaults to major version 7.
 
-## Backward Incompatibilities {#sec-release-23.11-incompatibilities}
+- [glibc](https://www.gnu.org/software/libc/) has been updated from v2.37 to
+  v2.38. Refer to the [the release
+  notes](https://sourceware.org/glibc/wiki/Release/2.38) for more details.
+
+- `linuxPackages_testing_bcachefs` is now soft-deprecated by
+  `linuxPackages_testing`.
+  - Please consider changing your NixOS configuration's `boot.kernelPackages`
+    to `linuxPackages_testing` until a stable kernel with bcachefs support is
+    released.
+
+- PostgreSQL now defaults to major version 15.
+
+- All [ROCm](https://rocm.docs.amd.com/en/latest/) packages have been updated
+  to v5.7.0.
+  - [ROCm](https://rocm.docs.amd.com/en/latest/) package attribute sets are
+    versioned: `rocmPackages` -> `rocmPackages_5`.
+
+- [systemd](https://systemd.io) has been updated from v253 to v254, refer to
+  [the release
+  notes](https://github.com/systemd/systemd/blob/v254/NEWS#L3-L659) for more
+  details.
+    - `boot.resumeDevice` **must be specified** when hibernating if not in EFI
+      mode.
+    - systemd may warn your system about the permissions of your ESP partition
+      (often `/boot`), this warning can be ignored for now, we are looking into
+      a satisfying solution regarding this problem.
+    - Updating with `nixos-rebuild boot` and rebooting is recommended, since in
+      some rare cases the `nixos-rebuild switch` into the new generation on a
+      live system might fail due to missing mount units.
+
+- If the user has a custom shell enabled via `users.users.${USERNAME}.shell =
+  ${CUSTOMSHELL}`, the assertion will require them to also set
+  `programs.${CUSTOMSHELL}.enable = true`. This is generally safe behavior, but
+  for anyone needing to opt out from the check
+  `users.users.${USERNAME}.ignoreShellProgramCheck = true` will do the job.
+
+- `yarn-berry` has been updated to v4.0.1. This means that NodeJS versions less
+  v18.12 are no longer supported by it. Refer to the [upstream
+  changelog](https://github.com/yarnpkg/berry/blob/master/CHANGELOG.md) for
+  more details.
+
+- GNOME has been updated to v45. Refer to the [release
+  notes](https://release.gnome.org/45/) for more details. Notably, Loupe has
+  replaced Eye of GNOME as the default image viewer, Snapshot has replaced
+  Cheese as the default camera application, and Photos will no longer be
+  installed.
+
+- The module [services.ankisyncd](#opt-services.ankisyncd.package) has been
+  switched to
+  [anki-sync-server-rs](https://github.com/ankicommunity/anki-sync-server-rs).
+  The former version written in Python was difficult to update, did not receive
+  updates in a while, and did not support recent versions of Anki.
+
+  Unfortunately all servers supporting new clients do not support the older
+  sync protocol that was used in the old server. This includes newer version of
+  anki-sync-server, Anki's built in sync server and this new Rust package. Thus
+  old clients will also need updating. In particular nixpkgs's Anki package is
+  also being updated in this release.
+
+  The module update takes care of the new config syntax. The data itself (i.e.
+  user login and card information) is compatible. Thus users of the module will
+  be able to simply log in again after updating both client and server without
+  any extra action needed to be taken.
+
+- The argument `vendorSha256` of `buildGoModule` is deprecated. Use
+  `vendorHash` instead. Refer to [PR
+  \#259999](https://github.com/NixOS/nixpkgs/pull/259999)) for more details.
+
+- `go-modules` in `buildGoModule` attrs has been renamed to `goModules`.
+
+- The package `cawbird` is dropped from nixpkgs. It broke by the Twitter API
+  closing down and has been abandoned upstream.
+
+- The Cinnamon module now enables XDG desktop integration by default. If you
+  are experiencing collisions related to xdg-desktop-portal-gtk you can safely
+  remove `xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];` from your
+  NixOS configuration.
+
+- GNOME, Pantheon, Cinnamon modules no longer force Qt applications to use
+  Adwaita style. This implemantion  was buggy and is no longer maintained
+  upstream. Specifically, Cinnamon defaults to the gtk2 style instead now,
+  following the default in Linux Mint). If you still want Adwaita used, you may
+  add the following options to your configuration. Please be aware, that it
+  will probably be removed eventually.
 
-- The `boot.loader.raspberryPi` options have been marked deprecated, with intent for removal for NixOS 24.11. They had a limited use-case, and do not work like people expect. They required either very old installs ([before mid-2019](https://github.com/NixOS/nixpkgs/pull/62462)) or customized builds out of scope of the standard and generic AArch64 support. That option set never supported the Raspberry Pi 4 family of devices.
+  ```nix
+  qt = {
+    enable = true;
+    platformTheme = "gnome";
+    style = "adwaita";
+  };
+  ```
 
-- `python3.pkgs.sequoia` was removed in favor of `python3.pkgs.pysequoia`. The latter package is based on upstream's dedicated repository for sequoia's Python bindings, where the Python bindings from [gitlab:sequoia-pgp/sequoia](https://gitlab.com/sequoia-pgp/sequoia) were removed long ago.
+- DocBook option documentation is no longer supported, all module documentation
+  now uses Markdown.
 
-- `writeTextFile` now requires `executable` to be boolean, values like `null` or `""` will now fail to evaluate.
+- Docker defaults to v24 now, as 20.10 is stopping to receive security updates
+  and bug fixes after [December 10,
+  2023](https://github.com/moby/moby/discussions/45104).
 
-- The latest version of `clonehero` now stores custom content in `~/.clonehero`. See the [migration instructions](https://clonehero.net/2022/11/29/v23-to-v1-migration-instructions.html). Typically, these content files would exist along side the binary, but the previous build used a wrapper script that would store them in `~/.config/unity3d/srylain Inc_/Clone Hero`.
+- Elixir defaults to v1.15 now. Refer to their
+  [changelog](https://elixir-lang.org/blog/2023/06/19/elixir-v1-15-0-released/)
+  for more details.
 
-- The `services.hostapd` module was rewritten to support `passwordFile` like options, WPA3-SAE, and management of multiple interfaces. This breaks compatibility with older configurations.
-  - `hostapd` is now started with additional systemd sandbox/hardening options for better security.
-  - `services.hostapd.interface` was replaced with a per-radio and per-bss configuration scheme using [services.hostapd.radios](#opt-services.hostapd.radios).
-  - `services.hostapd.wpa` has been replaced by [services.hostapd.radios.&lt;name&gt;.networks.&lt;name&gt;.authentication.wpaPassword](#opt-services.hostapd.radios._name_.networks._name_.authentication.wpaPassword) and [services.hostapd.radios.&lt;name&gt;.networks.&lt;name&gt;.authentication.saePasswords](#opt-services.hostapd.radios._name_.networks._name_.authentication.saePasswords) which configure WPA2-PSK and WP3-SAE respectively.
-  - The default authentication has been changed to WPA3-SAE. Options for other (legacy) schemes are still available.
+- The `extend` function of `llvmPackages` has been removed due it coming from
+  the `tools` attrset thus only extending the `tool` attrset. A possible
+  replacement is to construct the set from `libraries` and `tools`, or patch
+  nixpkgs.
 
-- `python3.pkgs.fetchPypi` (and `python3Packages.fetchPypi`) has been deprecated in favor of top-level `fetchPypi`.
+- `ffmpeg` defaults to `ffmpeg_6` now, upgrading from `ffmpeg_5`.
 
-- `pass` now does not contain `password-store.el`.  Users should get `password-store.el` from Emacs lisp package set `emacs.pkgs.password-store`.
+- `fontconfig` defaults to using greyscale antialiasing now. Previously
+  subpixel antialiasing was used because of a [recommendation from one of the
+  downstreams](https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/337).
+  You can change this value by configuring
+  [](#opt-fonts.fontconfig.subpixel.rgba) accordingly.
 
-- `mariadb` now defaults to `mariadb_1011` instead of `mariadb_106`, meaning the default version was upgraded from 10.6.x to 10.11.x. See the [upgrade notes](https://mariadb.com/kb/en/upgrading-from-mariadb-10-6-to-mariadb-10-11/) for potential issues.
+- The `fonts.fonts` and `fonts.enableDefaultFonts` options have been renamed to
+  `fonts.packages` and `fonts.enableDefaultPackages` respectively.
 
-- `getent` has been moved from `glibc`'s `bin` output to its own dedicated output, reducing closure size for many dependents. Dependents using the `getent` alias should not be affected; others should move from using `glibc.bin` or `getBin glibc` to `getent` (which also improves compatibility with non-glibc platforms).
+- `services.hedgedoc` has been heavily refactored, reducing the amount of
+  declared options in the module. Most of the options should still work without
+  any changes to the configuration. Some options have been deprecated, as they
+  no longer have any effect. Refer to [PR
+  #244941](https://github.com/NixOS/nixpkgs/pull/244941) for more details.
 
-- The `services.ananicy.extraRules` option now has the type of `listOf attrs` instead of `string`.
+- `jq` was updated to v1.7. This is its [first release in 5
+  years](https://github.com/jqlang/jq/releases/tag/jq-1.7).
 
-- The `matrix-synapse` package & module have undergone some significant internal changes, for most setups no intervention is needed, though:
-  - The option [`services.matrix-synapse.package`](#opt-services.matrix-synapse.package) is now read-only. For modifying the package, use an overlay which modifies `matrix-synapse-unwrapped` instead. More on that below.
-  - The `enableSystemd` & `enableRedis` arguments have been removed and `matrix-synapse` has been renamed to `matrix-synapse-unwrapped`. Also, several optional dependencies (such as `psycopg2` or `authlib`) have been removed.
-  - These optional dependencies are automatically added via a wrapper (`pkgs.matrix-synapse.override { extras = ["redis"]; }` for `hiredis` & `txredisapi` for instance) if the relevant config section is declared in `services.matrix-synapse.settings`. For instance, if `services.matrix-synapse.settings.redis.enabled` is set to `true`, `"redis"` will be automatically added to the `extras` list of `pkgs.matrix-synapse`.
-  - A list of all extras (and the extras enabled by default) can be found at the [option's reference for `services.matrix-synapse.extras`](#opt-services.matrix-synapse.extras).
-  - In some cases (e.g. for running synapse workers) it was necessary to re-use the `PYTHONPATH` of `matrix-synapse.service`'s environment to have all plugins available. This isn't necessary anymore, instead `config.services.matrix-synapse.package` can be used as it points to the wrapper with properly configured `extras` and also all plugins defined via [`services.matrix-synapse.plugins`](#opt-services.matrix-synapse.plugins) available. This is also the reason for why the option is read-only now, it's supposed to be set by the module only.
+- [`lib.attrsets.foldlAttrs`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.attrsets.foldlAttrs)
+  now always evaluates the initial accumulator argument first.
 
-- `etcd` has been updated to 3.5, you will want to read the [3.3 to 3.4](https://etcd.io/docs/v3.5/upgrades/upgrade_3_4/) and [3.4 to 3.5](https://etcd.io/docs/v3.5/upgrades/upgrade_3_5/) upgrade guides
+- [`lib.lists.foldl'`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.foldl-prime)
+  now always evaluates the initial accumulator argument first. If you depend on
+  the lazier behavior, consider using
+  [`lib.lists.foldl`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.foldl)
+  or
+  [`builtins.foldl'`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-foldl')
+  instead.
 
-- `consul` has been updated to `1.16.0`. See the [release note](https://github.com/hashicorp/consul/releases/tag/v1.16.0) for more details. Once a new Consul version has started and upgraded its data directory, it generally cannot be downgraded to the previous version.
+-  Now `magma` defaults to `magma-hip` instead of `magma-cuda`. It also
+   respects the `config.cudaSupport` and `config.rocmSupport` options.
 
-- `himalaya` has been updated to `0.8.0`, which drops the native TLS support (in favor of Rustls) and add OAuth 2.0 support. See the [release note](https://github.com/soywod/himalaya/releases/tag/v0.8.0) for more details.
+- The MariaDB C client library was upgraded from v3.2.x to v3.3.x. Refer to the
+  [upstream release
+  notes](https://mariadb.com/kb/en/mariadb-connector-c-33-release-notes/) for
+  more details.
 
-- The [services.caddy.acmeCA](#opt-services.caddy.acmeCA) option now defaults to `null` instead of `"https://acme-v02.api.letsencrypt.org/directory"`, to use all of Caddy's default ACME CAs and enable Caddy's automatic issuer fallback feature by default, as recommended by upstream.
+- Mattermost has been upgraded to extended support version 8.1 as the previously
+  packaged extended support version 7.8 is [reaching end-of-life](https://docs.mattermost.com/upgrade/extended-support-release.html).
+  Migration may take some time, refer to the [changelog](https://docs.mattermost.com/install/self-managed-changelog.html#release-v8-1-extended-support-release)
+  and [important upgrade notes](https://docs.mattermost.com/upgrade/important-upgrade-notes.html).
 
-- The default priorities of [`services.nextcloud.phpOptions`](#opt-services.nextcloud.phpOptions) have changed. This means that e.g.
-  `services.nextcloud.phpOptions."opcache.interned_strings_buffer" = "23";` doesn't discard all of the other defaults from this option
-  anymore. The attribute values of `phpOptions` are still defaults, these can be overridden as shown here.
+- The `netdata` package disables cloud support by default now. To enable it use the `netdataCloud` package.
 
-  To override all of the options (including including `upload_max_filesize`, `post_max_size`
-  and `memory_limit` which all point to [`services.nextcloud.maxUploadSize`](#opt-services.nextcloud.maxUploadSize)
-  by default) can be done like this:
+- `networking.nftables` is no longer flushing all rulesets on every reload.
+  Use `networking.nftables.flushRuleset = true;` to enable the previous behaviour.
+
+- Node.js v14, v16 has been removed as they were end of life. Any dependent packages that contributors were not able to reasonably upgrade were dropped after a month of notice to their maintainers, were **removed**.
+  - This includes VSCode Server.
+  - This includes Kibana 7 as the ELK stack is unmaintained in nixpkgs and is marked for slow removal.
+
+- The application firewall `opensnitch` uses the process monitor method eBPF as
+  default now. This is recommended by upstream. The method may be changed with
+  the setting
+  [services.opensnitch.settings.ProcMonitorMethod](#opt-services.opensnitch.settings.ProcMonitorMethod).
+
+- `paperwork` is updated to v2.2. Documents scanned with this version will not
+  be visible to previous versions if you downgrade. Refer to the [upstream
+  announcement](https://forum.openpaper.work/t/paperwork-2-2-testing-phase/316#important-switch-from-jpeg-to-png-for-new-pages-2)
+  for details and workarounds.
+
+- The latest available version of Nextcloud is v27 (available as
+  `pkgs.nextcloud27`). The installation logic is as follows:
+  - If [`services.nextcloud.package`](#opt-services.nextcloud.package) is
+    specified explicitly, this package will be installed (**recommended**)
+  - If [`system.stateVersion`](#opt-system.stateVersion) is >=23.11,
+    `pkgs.nextcloud27` will be installed by default.
+  - If [`system.stateVersion`](#opt-system.stateVersion) is >=23.05,
+    `pkgs.nextcloud26` will be installed by default.
+  - Please note that an upgrade from v25 (or older) to v27 is not possible
+    directly. Please upgrade to `nextcloud26` (or earlier) first. Nextcloud
+    prohibits skipping major versions while upgrading. You may upgrade by
+    declaring [`services.nextcloud.package =
+    pkgs.nextcloud26;`](options.html#opt-services.nextcloud.package) inbetween.
+
+- `postgresql_11` has been removed since it'll stop receiving fixes on November
+  9th 2023.
+
+- `programs.gnupg.agent.pinentryFlavor` is set in `/etc/gnupg/gpg-agent.conf`
+  now. It will no longer take precedence over a `pinentry-program` set in
+  `~/.gnupg/gpg-agent.conf`.
+
+- `python3.pkgs.flitBuildHook` has been removed. Use `flit-core` and `format =
+  "pyproject"` instead.
+
+- Certificate generation via the `security.acme` limits the concurrent number
+  of running certificate renewals and generation jobs now. This is to avoid
+  spiking resource usage when processing many certificates at once. The limit
+  defaults to *5* and can be adjusted via `maxConcurrentRenewals`. Setting the
+  value to *0* disables the limits altogether.
+
+- `services.borgmatic.settings.location` and
+  `services.borgmatic.configurations.<name>.location` are deprecated, please
+  move your options out of sections to the global scope.
+
+- `services.fail2ban.jails` can be configured with attribute sets now, defining
+  settings and filters instead of lines. The stringed options `daemonConfig`
+  and `extraSettings` have respectively been replaced by `daemonSettings` and
+  `jails.DEFAULT.settings`. Those  use attribute sets.
+
+- The `services.mbpfan` module has the option `aggressive` enabled by default
+  now. This is for better heat moderation. To get the upstream defaults you may
+  disable this.
+
+- Apptainer/Singularity defaults to using `"$out/var/lib"` for the
+  `LOCALSTATEDIR` configuration option instead of the top-level `"/var/lib"`
+  now. This change impacts the `SESSIONDIR` (container-run-time mount point)
+  configuration, which is set to `$LOCALSTATEDIR/<apptainer or
+  singularity>/mnt/session`. This detaches the packages from the top-level
+  directory, rendering the NixOS module optional.
+
+  The default behavior of the NixOS module `programs.singularity` stays
+  unchanged. We add a new option
+  `programs.singularity.enableExternalSysConfDir` (default to `true`) to
+  specify whether to set the top-level `"/var/lib"` as `LOCALSTATEDIR` or not.
+
+- The `services.sslh` module has been updated to follow [RFC
+  0042](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md).
+  As such, several options have been moved to the freeform attribute set
+  [services.sslh.settings](#opt-services.sslh.settings), which allows to change
+  any of the settings in {manpage}`sslh(8)`.
+
+  In addition, the newly added option
+  [services.sslh.method](#opt-services.sslh.method) allows to switch between
+  the {manpage}`fork(2)`, {manpage}`select(2)` and `libev`-based connection
+  handling method. Refer to the [sslh
+  docs](https://github.com/yrutschle/sslh/blob/master/doc/INSTALL.md#binaries)
+  for a comparison.
+
+- Suricata was upgraded from v6.0 to v7.0 and no longer considers HTTP/2
+  support as experimental. Refer to [upstream release
+  notes](https://forum.suricata.io/t/suricata-7-0-0-released/3715) for more
+  details.
+
+- `teleport` has been upgraded from major version 12 to major version 14.
+  Refer to upstream [upgrade
+  instructions](https://goteleport.com/docs/management/operations/upgrading/)
+  and release notes for
+  [v13](https://goteleport.com/docs/changelog/#1300-050823) and
+  [v14](https://goteleport.com/docs/changelog/#1400-092023). Note that Teleport
+  does not officially support upgrades across more than one major version at a
+  time. If you're running Teleport server components, it is recommended to
+  first upgrade to an intermediate v13.x version by setting
+  `services.teleport.package = pkgs.teleport_13`. Afterwards, this option can
+  be removed to upgrade to the default version (14).
+
+- `zfs` was updated from v2.1.x to v2.2.0, [enabling newer kernel support and
+  adding new features](https://github.com/openzfs/zfs/releases/tag/zfs-2.2.0).
+
+- The use of `sourceRoot = "source";`, `sourceRoot = "source/subdir";`, and
+  similar lines in package derivations using the default `unpackPhase` is
+  deprecated as it requires `unpackPhase` to always produce a directory named
+  "source". Use `sourceRoot = src.name`, `sourceRoot = "${src.name}/subdir";`,
+  or `setSourceRoot = "sourceRoot=$(echo */subdir)";` or similar instead.
+
+- The `django` alias in the python package set was upgraded to Django v4.x.
+  Applications that consume Django should always pin their python environment
+  to a compatible major version, so they can move at their own pace.
 
   ```nix
-  {
-    services.nextcloud.phpOptions = lib.mkForce {
-      /* ... */
+  python = python3.override {
+    packageOverrides = self: super: {
+      django = super.django_3;
     };
-  }
+  };
   ```
 
-- `php80` is no longer supported due to upstream not supporting this version anymore.
-
-- PHP now defaults to PHP 8.2, updated from 8.1.
-
-- The ISC DHCP package and corresponding module have been removed, because they are end of life upstream. See https://www.isc.org/blogs/isc-dhcp-eol/ for details and switch to a different DHCP implementation like kea or dnsmasq.
-
-- `prometheus-unbound-exporter` has been replaced by the Let's Encrypt maintained version, since the previous version was archived. This requires some changes to the module configuration, most notable `controlInterface` needs migration
-   towards `unbound.host` and requires either the `tcp://` or `unix://` URI scheme.
-
-- `odoo` now defaults to 16, updated from 15.
-
-- `util-linux` is now supported on Darwin and is no longer an alias to `unixtools`. Use the `unixtools.util-linux` package for access to the Apple variants of the utilities.
-
-- `services.keyd` changed API. Now you can create multiple configuration files.
-
-- `baloo`, the file indexer/search engine used by KDE now has a patch to prevent files from constantly being reindexed when the device ids of the their underlying storage changes. This happens frequently when using btrfs or LVM. The patch has not yet been accepted upstream but it provides a significantly improved experience. When upgrading, reset baloo to get a clean index: `balooctl disable ; balooctl purge ; balooctl enable`.
-
-- `services.ddclient` has been removed on the request of the upstream maintainer because it is unmaintained and has bugs. Please switch to a different software like `inadyn` or `knsupdate`.
-
-- The `vlock` program from the `kbd` package has been moved into its own package output and should now be referenced explicitly as `kbd.vlock` or replaced with an alternative such as the standalone `vlock` package or `physlock`.
-
-- `fileSystems.<name>.autoFormat` now uses `systemd-makefs`, which does not accept formatting options. Therefore, `fileSystems.<name>.formatOptions` has been removed.
-
-- `fileSystems.<name>.autoResize` now uses `systemd-growfs` to resize the file system online in stage 2. This means that `f2fs` and `ext2` can no longer be auto resized, while `xfs` and `btrfs` now can be.
-
-- The `services.vaultwarden.config` option default value was changed to make Vaultwarden only listen on localhost, following the [secure defaults for most NixOS services](https://github.com/NixOS/nixpkgs/issues/100192).
-
-- `services.lemmy.settings.federation` was removed in 0.17.0 and no longer has any effect. To enable federation, the hostname must be set in the configuration file and then federation must be enabled in the admin web UI. See the [release notes](https://github.com/LemmyNet/lemmy/blob/c32585b03429f0f76d1e4ff738786321a0a9df98/RELEASES.md#upgrade-instructions) for more details.
+- The `qemu-vm.nix` module by default now identifies block devices via
+  persistent names available in `/dev/disk/by-*`. Because the rootDevice is
+  identified by its filesystem label, it needs to be formatted before the VM is
+  started. The functionality of automatically formatting the rootDevice in the
+  initrd is removed from the QEMU module. However, for tests that depend on
+  this functionality, a test utility for the scripted initrd is added
+  (`nixos/tests/common/auto-format-root-device.nix`). To use this in a NixOS
+  test, import the module, e.g. `imports = [
+  ./common/auto-format-root-device.nix ];` When you use the systemd initrd, you
+  can automatically format the root device by setting
+  `virtualisation.fileSystems."/".autoFormat = true;`.
 
-- `pict-rs` was upgraded from 0.3 to 0.4 and contains an incompatible database & configuration change. To upgrade on systems with `stateVersion = "23.05";` or older follow the migration steps from https://git.asonix.dog/asonix/pict-rs#user-content-0-3-to-0-4-migration-guide and set `services.pict-rs.package = pkgs.pict-rs;`.
+- The `electron` packages places its application files in
+  `$out/libexec/electron` instead of `$out/lib/electron` now. Packages using
+  electron-builder will fail to build and need to be adjusted by changing `lib`
+  to `libexec`.
 
-- The following packages in `haskellPackages` have now a separate bin output: `cabal-fmt`, `calligraphy`, `eventlog2html`, `ghc-debug-brick`, `hindent`, `nixfmt`, `releaser`. This means you need to replace e.g. `"${pkgs.haskellPackages.nixfmt}/bin/nixfmt"` with `"${lib.getBin pkgs.haskellPackages.nixfmt}/bin/nixfmt"` or `"${lib.getExe pkgs.haskellPackages.nixfmt}"`. The binaries also won’t be in scope if you rely on them being installed e.g. via `ghcWithPackages`. `environment.packages` picks the `bin` output automatically, so for normal installation no intervention is required. Also, toplevel attributes like `pkgs.nixfmt` are not impacted negatively by this change.
+### New Services {#sec-release-23.11-nixos-new-services}
 
-- `spamassassin` no longer supports the `Hashcash` module. The module needs to be removed from the `loadplugin` list if it was copied over from the default `initPreConf` option.
+- [MCHPRS](https://github.com/MCHPR/MCHPRS), a multithreaded Minecraft server
+  built for redstone. Available as
+  [services.mchprs](#opt-services.mchprs.enable).
 
-- `services.outline.sequelizeArguments` has been removed, as `outline` no longer executes database migrations via the `sequelize` cli.
+- [acme-dns](https://github.com/joohoi/acme-dns), a limited DNS server to
+  handle ACME DNS challenges easily and securely. Available as
+  [services.acme-dns](#opt-services.acme-dns.enable).
 
-- The binary of the package `cloud-sql-proxy` has changed from `cloud_sql_proxy` to `cloud-sql-proxy`.
+- [frp](https://github.com/fatedier/frp), a fast reverse proxy to help you
+  expose a local server behind a NAT or firewall to the Internet. Available as
+  [services.frp](#opt-services.frp.enable).
 
-- The `woodpecker-*` CI packages have been updated to 1.0.0. This release is wildly incompatible with the 0.15.X versions that were previously packaged. Please read [upstream's documentation](https://woodpecker-ci.org/docs/next/migrations#100) to learn how to update your CI configurations.
+- [river](https://github.com/riverwm/river), A dynamic tiling wayland
+  compositor. Available as [programs.river](#opt-programs.river.enable).
 
-- The Caddy module gained a new option named `services.caddy.enableReload` which is enabled by default. It allows reloading the service instead of restarting it, if only a config file has changed. This option must be disabled if you have turned off the [Caddy admin API](https://caddyserver.com/docs/caddyfile/options#admin). If you keep this option enabled, you should consider setting [`grace_period`](https://caddyserver.com/docs/caddyfile/options#grace-period) to a non-infinite value to prevent Caddy from delaying the reload indefinitely.
+- [wayfire](https://wayfire.org), a modular and extensible wayland compositor.
+  Available as [programs.wayfire](#opt-programs.wayfire.enable).
 
-- mdraid support is now optional. This reduces initramfs size and prevents the potentially undesired automatic detection and activation of software RAID pools. It is disabled by default in new configurations (determined by `stateVersion`), but the appropriate settings will be generated by `nixos-generate-config` when installing to a software RAID device, so the standard installation procedure should be unaffected. If you have custom configs relying on mdraid, ensure that you use `stateVersion` correctly or set `boot.swraid.enable` manually.
+- [mautrix-whatsapp](https://docs.mau.fi/bridges/go/whatsapp/index.html), a
+  Matrix-WhatsApp puppeting bridge. Available as
+  [services.mautrix-whatsapp](#opt-services.mautrix-whatsapp.enable).
 
-- The `go-ethereum` package has been updated to v1.12.0. This drops support for proof-of-work. Its GraphQL API now encodes all numeric values as hex strings and the GraphQL UI is updated to version 2.0. The default database has changed from `leveldb` to `pebble` but `leveldb` can be forced with the --db.engine=leveldb flag. The `checkpoint-admin` command was [removed along with trusted checkpoints](https://github.com/ethereum/go-ethereum/pull/27147).
+- [hddfancontrol](https://github.com/desbma/hddfancontrol), a service to
+  regulate fan speeds based on hard drive temperature. Available as
+  [services.hddfancontrol](#opt-services.hddfancontrol.enable).
 
-- The `aseprite-unfree` package has been upgraded from 1.2.16.3 to 1.2.40. The free version of aseprite has been dropped because it is EOL and the package attribute now points to the unfree version. A maintained fork of the last free version of Aseprite, named 'LibreSprite', is available in the `libresprite` package.
+- [seatd](https://sr.ht/~kennylevinsen/seatd/), A minimal seat management
+  daemon. Available as [services.seatd](#opt-services.seatd.enable).
 
-- The default `kops` version is now 1.27.0 and support for 1.24 and older has been dropped.
+- [GoToSocial](https://gotosocial.org/), an ActivityPub social network server
+  written in Golang. Available as
+  [services.gotosocial](#opt-services.gotosocial.enable).
 
-- `pharo` has been updated to latest stable (PharoVM 10.0.5), which is compatible with the latest stable and oldstable images (Pharo 10 and 11). The VM in question is the 64bit Spur. The 32bit version has been dropped due to lack of maintenance. The Cog VM has been deleted because it is severily outdated. Finally, the `pharo-launcher` package has been deleted because it was not compatible with the newer VM, and due to lack of maintenance.
+- [Castopod](https://castopod.org/), an open-source hosting platform made for
+  podcasters who want to engage and interact with their audience. Available as
+  [services.castopod](#opt-services.castopod.enable).
 
-- Emacs mainline version 29 was introduced. This new version includes many major additions, most notably `tree-sitter` support (enabled by default) and the pgtk variant (useful for Wayland users), which is available under the attribute `emacs29-pgtk`.
+- [Typesense](https://github.com/typesense/typesense), a fast, typo-tolerant
+  search engine for building delightful search experiences. Available as
+  [services.typesense](#opt-services.typesense.enable).
 
-- Emacs macport version 29 was introduced.
+* [NS-USBLoader](https://github.com/developersu/ns-usbloader/), an all-in-one
+  tool for managing Nintendo Switch homebrew. Available as
+  [programs.ns-usbloader](#opt-programs.ns-usbloader.enable).
 
-- The `html-proofer` package has been updated from major version 3 to major version 5, which includes [breaking changes](https://github.com/gjtorikian/html-proofer/blob/v5.0.8/UPGRADING.md).
+- [athens](https://github.com/gomods/athens), a Go module datastore and proxy. Available as [services.athens](#opt-services.athens.enable).
 
-## Other Notable Changes {#sec-release-23.11-notable-changes}
+- [Mobilizon](https://joinmobilizon.org/), a Fediverse platform for publishing
+  events. Available as [services.mobilizon](#opt-services.mobilizon.enable).
 
-- The Cinnamon module now enables XDG desktop integration by default. If you are experiencing collisions related to xdg-desktop-portal-gtk you can safely remove `xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];` from your NixOS configuration.
+- [Anuko Time Tracker](https://github.com/anuko/timetracker), a simple, easy to
+  use, open source time tracking system. Available as
+  [services.anuko-time-tracker](#opt-services.anuko-time-tracker.enable).
 
-- GNOME, Pantheon, Cinnamon module no longer forces Qt applications to use Adwaita style since it was buggy and is no longer maintained upstream (specifically, Cinnamon now defaults to the gtk2 style instead, following the default in Linux Mint). If you still want it, you can add the following options to your configuration but it will probably be eventually removed:
+- [Prometheus MySQL exporter](https://github.com/prometheus/mysqld_exporter), a
+  MySQL server exporter for Prometheus. Available as
+  [services.prometheus.exporters.mysqld](#opt-services.prometheus.exporters.mysqld.enable).
 
-  ```nix
-  qt = {
-    enable = true;
-    platformTheme = "gnome";
-    style = "adwaita";
-  };
-  ```
+- [LibreNMS](https://www.librenms.org), a auto-discovering PHP/MySQL/SNMP based
+  network monitoring. Available as
+  [services.librenms](#opt-services.librenms.enable).
 
-- `fontconfig` now defaults to using greyscale antialiasing instead of subpixel antialiasing because of a [recommendation from one of the downstreams](https://gitlab.freedesktop.org/fontconfig/fontconfig/-/issues/337). You can change this value by configuring [](#opt-fonts.fontconfig.subpixel.rgba) accordingly.
+- [Livebook](https://livebook.dev/), an interactive notebook with support for
+  Elixir, graphs, machine learning, and more. Available as
+  [services.livebook](#opt-services.livebook.enableUserService).
 
-- The latest available version of Nextcloud is v27 (available as `pkgs.nextcloud27`). The installation logic is as follows:
-  - If [`services.nextcloud.package`](#opt-services.nextcloud.package) is specified explicitly, this package will be installed (**recommended**)
-  - If [`system.stateVersion`](#opt-system.stateVersion) is >=23.11, `pkgs.nextcloud27` will be installed by default.
-  - If [`system.stateVersion`](#opt-system.stateVersion) is >=23.05, `pkgs.nextcloud26` will be installed by default.
-  - Please note that an upgrade from v25 (or older) to v27 directly is not possible. Please upgrade to `nextcloud26` (or earlier) first. Nextcloud prohibits skipping major versions while upgrading. You can upgrade by declaring [`services.nextcloud.package = pkgs.nextcloud26;`](options.html#opt-services.nextcloud.package).
+- [sitespeed-io](https://sitespeed.io), a tool that can generate metrics such
+  as timings and diagnostics for websites. Available as
+  [services.sitespeed-io](#opt-services.sitespeed-io.enable).
 
-- New options were added to `services.searx` for better SearXNG support, including options for the built-in rate limiter and bot protection and automatically configuring a local redis server.
+- [stalwart-mail](https://stalw.art), an all-in-one email server (SMTP, IMAP,
+  JMAP). Available as
+  [services.stalwart-mail](#opt-services.stalwart-mail.enable).
 
-- A new option was added to the virtualisation module that enables specifying explicitly named network interfaces in QEMU VMs. The existing `virtualisation.vlans` is still supported for cases where the name of the network interface is irrelevant.
 
-- DocBook option documentation is no longer supported, all module documentation now uses markdown.
+- [tang](https://github.com/latchset/tang), a server for binding data to
+  network presence. Available as [services.tang](#opt-services.tang.enable).
 
-- `buildGoModule` `go-modules` attrs have been renamed to `goModules`.
+- [Jool](https://nicmx.github.io/Jool/en/index.html), a kernelspace NAT64 and
+  SIIT implementation providing translation between IPv4 and IPv6. Available as
+  [networking.jool.enable](#opt-networking.jool.enable).
 
-- The `fonts.fonts` and `fonts.enableDefaultFonts` options have been renamed to `fonts.packages` and `fonts.enableDefaultPackages` respectively.
+- [Home Assistant
+  Satellite](https://github.com/synesthesiam/homeassistant-satellite), a
+  streaming audio satellite for Home Assistant voice pipelines, where you can
+  reuse existing mic and speaker hardware. Available as
+  [services.homeassistant-satellite](#opt-services.homeassistant-satellite.enable).
 
-- `services.fail2ban.jails` can now be configured with attribute sets defining settings and filters instead of lines. The stringed options `daemonConfig` and `extraSettings` have respectively been replaced by `daemonSettings` and `jails.DEFAULT.settings` which use attribute sets.
+- [Apache Guacamole](https://guacamole.apache.org/), a cross-platform,
+  clientless remote desktop gateway. Available as
+  [services.guacamole-server](#opt-services.guacamole-server.enable) and
+  [services.guacamole-client](#opt-services.guacamole-client.enable) services.
 
-- The application firewall `opensnitch` now uses the process monitor method eBPF as default as recommended by upstream. The method can be changed with the setting [services.opensnitch.settings.ProcMonitorMethod](#opt-services.opensnitch.settings.ProcMonitorMethod).
+- [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler.
+  Available as [services.pgbouncer](#opt-services.pgbouncer.enable).
 
-- The module [services.ankisyncd](#opt-services.ankisyncd.package) has been switched to [anki-sync-server-rs](https://github.com/ankicommunity/anki-sync-server-rs) from the old python version, which was difficult to update, had not been updated in a while, and did not support recent versions of anki.
-Unfortunately all servers supporting new clients (newer version of anki-sync-server, anki's built in sync server and this new rust package) do not support the older sync protocol that was used in the old server, so such old clients will also need updating and in particular the anki package in nixpkgs is also being updated in this release.
-The module update takes care of the new config syntax and the data itself (user login and cards) are compatible, so users of the module will be able to just log in again after updating both client and server without any extra action.
+- [Goss](https://goss.rocks/), a YAML based serverspec alternative tool for
+  validating a server's configuration. Available as
+  [services.goss](#opt-services.goss.enable).
 
-- `services.nginx` gained a `defaultListen` option at server-level with support for PROXY protocol listeners, also `proxyProtocol` is now exposed in `services.nginx.virtualHosts.<name>.listen` option. It is now possible to run PROXY listeners and non-PROXY listeners at a server-level, see [#213510](https://github.com/NixOS/nixpkgs/pull/213510/) for more details.
+- [trust-dns](https://trust-dns.org/), a Rust based DNS server built to be safe
+  and secure from the ground up. Available as
+  [services.trust-dns](#opt-services.trust-dns.enable).
 
-- `services.prometheus.exporters` has a new exporter to monitor electrical power consumption based on PowercapRAPL sensor called [Scaphandre](https://github.com/hubblo-org/scaphandre), see [#239803](https://github.com/NixOS/nixpkgs/pull/239803) for more details.
+- [osquery](https://www.osquery.io/), a SQL powered operating system
+  instrumentation, monitoring, and analytics. Available as
+  [services.osquery](#opt-services.osquery.enable).
 
-- The MariaDB C client library was upgraded from 3.2.x to 3.3.x. It is recomended to review the [upstream release notes](https://mariadb.com/kb/en/mariadb-connector-c-33-release-notes/).
+- [ebusd](https://ebusd.eu), a daemon for handling communication with eBUS
+  devices connected to a 2-wire bus system ("energy bus" used by numerous
+  heating systems). Available as [services.ebusd](#opt-services.ebusd.enable).
 
-- The module `services.calibre-server` has new options to configure the `host`, `port`, `auth.enable`, `auth.mode` and `auth.userDb` path, see [#216497](https://github.com/NixOS/nixpkgs/pull/216497/) for more details.
+- [systemd-sysupdate](https://www.freedesktop.org/software/systemd/man/systemd-sysupdate.html),
+  atomically updates the host OS, container images, portable service images or
+  other sources. Available as [systemd.sysupdate](opt-systemd.sysupdate).
 
-- `services.prometheus.exporters` has a new [exporter](https://github.com/hipages/php-fpm_exporter) to monitor PHP-FPM processes, see [#240394](https://github.com/NixOS/nixpkgs/pull/240394) for more details.
+- [eris-server](https://codeberg.org/eris/eris-go), an implementation of the
+  Encoding for Robust Immutable Storage (ERIS). Available as
+  [services.eris-server](#opt-services.eris-server.enable).
 
-- `services.github-runner` / `services.github-runners.<name>` gained the option `nodeRuntimes`. The option defaults to `[ "node20" ]`, i.e., the service supports Node.js 20 GitHub Actions only. The list of Node.js versions accepted by `nodeRuntimes` tracks the versions the upstream GitHub Actions runner supports. See [#249103](https://github.com/NixOS/nixpkgs/pull/249103) for details.
+- [forgejo](https://forgejo.org/), a git forge and drop-in replacement for
+  Gitea. Available as [services.forgejo](#opt-services.forgejo.enable).
 
-- `programs.gnupg.agent.pinentryFlavor` is now set in `/etc/gnupg/gpg-agent.conf`, and will no longer take precedence over a `pinentry-program` set in `~/.gnupg/gpg-agent.conf`.
+- `hardware/infiniband.nix` adds infiniband subnet manager support using an
+  [opensm](https://github.com/linux-rdma/opensm) systemd-template service,
+  instantiated on card guids. The module also adds kernel modules and cli
+  tooling to help administrators debug and measure performance. Available as
+  [hardware.infiniband.enable](#opt-hardware.infiniband.enable).
 
-- `services.influxdb2` now supports doing an automatic initial setup and provisioning of users, organizations, buckets and authentication tokens, see [#249502](https://github.com/NixOS/nixpkgs/pull/249502) for more details.
+- [zwave-js](https://github.com/zwave-js/zwave-js-server), a small server
+  wrapper around Z-Wave JS to access it via a WebSocket. Available as
+  [services.zwave-js](#opt-services.zwave-js.enable).
 
-- `wrapHelm` now exposes `passthru.pluginsDir` which can be passed to `helmfile`. For convenience, a top-level package `helmfile-wrapped` has been added, which inherits `passthru.pluginsDir` from `kubernetes-helm-wrapped`. See [#217768](https://github.com/NixOS/nixpkgs/issues/217768) for details.
+- [Honk](https://humungus.tedunangst.com/r/honk), a complete ActivityPub server
+  with minimal setup and support costs. Available as
+  [services.honk](#opt-services.honk.enable).
 
-- `boot.initrd.network.udhcp.enable` allows control over dhcp during stage 1 regardless of what `networking.useDHCP` is set to.
+- [ferretdb](https://www.ferretdb.io/), an open-source proxy, converting the
+  MongoDB 6.0+ wire protocol queries to PostgreSQL or SQLite. Available as
+  [services.ferretdb](options.html#opt-services.ferretdb.enable).
 
-- Suricata was upgraded from 6.0 to 7.0 and no longer considers HTTP/2 support as experimental, see [upstream release notes](https://forum.suricata.io/t/suricata-7-0-0-released/3715) for more details.
+- [MicroBin](https://microbin.eu/), a feature rich, performant and secure text
+  and file sharing web application, a "paste bin". Available as
+  [services.microbin](#opt-services.microbin.enable).
 
-- `networking.nftables` now has the option `networking.nftables.table.<table>` to create tables
+- [NNCP](http://www.nncpgo.org/), nncp-daemon and nncp-caller services.
+  Available as [programs.nncp.settings](#opt-programs.nncp.settings) and
+  [services.nncp](#opt-services.nncp.caller.enable).
+
+- [FastNetMon Advanced](https://fastnetmon.com/product-overview/), a commercial
+  high performance DDoS detector and sensor. Available as
+  [services.fastnetmon-advanced](#opt-services.fastnetmon-advanced.enable).
+
+- [tuxedo-rs](https://github.com/AaronErhardt/tuxedo-rs), Rust utilities for
+  interacting with hardware from TUXEDO Computers. Available as
+  [hardware.tuxedo-rs](#opt-hardware.tuxedo-rs.enable).
+
+- [certspotter](https://github.com/SSLMate/certspotter), a certificate
+  transparency log monitor. Available as
+  [services.certspotter](#opt-services.certspotter.enable).
+
+- [audiobookshelf](https://github.com/advplyr/audiobookshelf/), a self-hosted
+  audiobook and podcast server. Available as
+  [services.audiobookshelf](#opt-services.audiobookshelf.enable).
+
+- [ZITADEL](https://zitadel.com), a turnkey identity and access management
+  platform. Available as [services.zitadel](#opt-services.zitadel.enable).
+
+- [exportarr](https://github.com/onedr0p/exportarr), Prometheus Exporters for
+  Bazarr, Lidarr, Prowlarr, Radarr, Readarr, and Sonarr. Available as
+  [services.prometheus.exporters.exportarr-bazarr](#opt-services.prometheus.exporters.exportarr-bazarr.enable)/[services.prometheus.exporters.exportarr-lidarr](#opt-services.prometheus.exporters.exportarr-lidarr.enable)/[services.prometheus.exporters.exportarr-prowlarr](#opt-services.prometheus.exporters.exportarr-prowlarr.enable)/[services.prometheus.exporters.exportarr-radarr](#opt-services.prometheus.exporters.exportarr-radarr.enable)/[services.prometheus.exporters.exportarr-readarr](#opt-services.prometheus.exporters.exportarr-readarr.enable)/[services.prometheus.exporters.exportarr-sonarr](#opt-services.prometheus.exporters.exportarr-sonarr.enable).
+
+- [netclient](https://github.com/gravitl/netclient), an automated WireGuard
+  Management Client. Available as
+  [services.netclient](#opt-services.netclient.enable).
+
+- [trunk-ng](https://github.com/ctron/trunk), A fork of `trunk`: Build, bundle
+  & ship your Rust WASM application to the web
+
+- [virt-manager](https://virt-manager.org/), an UI for managing virtual
+  machines in libvirt. Available as
+  [programs.virt-manager](#opt-programs.virt-manager.enable).
+
+- [Soft Serve](https://github.com/charmbracelet/soft-serve), a tasty,
+  self-hostable Git server for the command line. Available as
+  [services.soft-serve](#opt-services.soft-serve.enable).
+
+- [Rosenpass](https://rosenpass.eu/), a service for post-quantum-secure VPNs
+  with WireGuard. Available as
+  [services.rosenpass](#opt-services.rosenpass.enable).
+
+- [c2FmZQ](https://github.com/c2FmZQ/c2FmZQ/), an application that can securely
+  encrypt, store, and share files, including but not limited to pictures and
+  videos. Available as
+  [services.c2fmzq-server](#opt-services.c2fmzq-server.enable).
+
+- [preload](http://sourceforge.net/projects/preload), a service that makes
+  applications run faster by prefetching binaries and shared objects.
+  Available as [services.preload](#opt-services.preload.enable).
+
+### Other Notable Changes {#sec-release-23.11-nixos-notable-changes}
+
+- The new option `system.switch.enable` was added. It is enabled by default.
+  Disabling it makes the system unable to be reconfigured via `nixos-rebuild`.
+  This is of advantage for image based appliances where updates are handled
+  outside the image.
+
+- `services.searx` receives new options for better SearXNG support. This
+  includes options for the built-in rate limiter, bot protection and
+  automatically configuring a local Redis server.
+
+- The iptables firewall module installs the `nixos-firewall-tool` now which
+  allows the user to easily temporarily open ports through the firewall.
+
+- A new option was added to the virtualisation module that enables specifying
+  explicitly named network interfaces in QEMU VMs. The existing
+  `virtualisation.vlans` is still supported for cases where the name of the
+  network interface is irrelevant.
+
+- `services.outline` can be configured to use local filesystem storage now.
+  Previously ony S3 storage was possible. This may be set using
+  [services.outline.storage.storageType](#opt-services.outline.storage.storageType).
+
+- `pkgs.openvpn3` optionally supports systemd-resolved now. `programs.openvpn3`
+  will automatically enable systemd-resolved support if
+  [services.resolved.enable](#opt-services.resolved.enable) is set to true.
+
+- The
+  [services.woodpecker-server.environmentFile](#opt-services.woodpecker-server.environmentFile)
+  type was changed to list of paths to be more consistent to the
+  woodpecker-agent module
+
+- `services.matrix-synapse` has new options to configure worker processes for
+  matrix-synapse using
+  [`services.matrix-synapse.workers`](#opt-services.matrix-synapse.workers).
+  Configuring a local redis server using
+  [`services.matrix-synapse.configureRedisLocally`](#opt-services.matrix-synapse.configureRedisLocally)
+  is also possible now.
+
+- The `services.nginx` module gained a `defaultListen` option at server-level
+  with support for PROXY protocol listeners. Also `proxyProtocol` is exposed in
+  the `services.nginx.virtualHosts.<name>.listen` option now. This it is
+  possible to run PROXY listeners and non-PROXY listeners at a server-level.
+  Refer to [PR #213510](https://github.com/NixOS/nixpkgs/pull/213510/) for more
+  details.
+
+- `services.restic.backups` adds wrapper scripts to your system path now. This
+  wrapper script sets the same environment variables as the service, so restic
+  operations can easily be run from the command line. This behavior can be
+  disabled by setting `createWrapper` to `false`, for each backup
+  configuration.
+
+- `services.prometheus.exporters` has a new exporter to monitor electrical
+  power consumption based on PowercapRAPL sensor called
+  [Scaphandre](https://github.com/hubblo-org/scaphandre). Refer to [PR
+  #239803](https://github.com/NixOS/nixpkgs/pull/239803) for more details.
+
+- The `services.calibre-server` module has new options to configure the `host`,
+  `port`, `auth.enable`, `auth.mode` and `auth.userDb` path. Refer to [PR
+  #216497](https://github.com/NixOS/nixpkgs/pull/216497/) for more details.
+
+- `services.prometheus.exporters` has a new
+  [exporter](https://github.com/hipages/php-fpm_exporter) to monitor PHP-FPM
+  processes. Refer to [PR
+  #240394](https://github.com/NixOS/nixpkgs/pull/240394) for more details.
+
+- `services.github-runner` and `services.github-runners.<name>` gained the
+  option `nodeRuntimes`. This option defaults to `[ "node20" ]`.  I.e., the
+  service supports Node.js 20 GitHub Actions only. The list of Node.js versions
+  accepted by `nodeRuntimes` tracks the versions the upstream GitHub Actions
+  runner supports. Refer to [PR
+  #249103](https://github.com/NixOS/nixpkgs/pull/249103) for details.
+
+- `programs.gnupg` has the option `agent.settings` now. This allows setting
+  verbatim config values in `/etc/gnupg/gpg-agent.conf`.
+
+- `dockerTools.buildImage`, `dockerTools.buildLayeredImage` and
+  `dockerTools.streamLayeredImage` use `lib.makeOverridable` now . This allows
+  `dockerTools`-based images to be customized more efficiently at the Nix
+  level.
+
+- `services.influxdb2` supports doing an automatic initial setup and
+  provisioning of users, organizations, buckets and authentication tokens now.
+  Refer to [PR #249502](https://github.com/NixOS/nixpkgs/pull/249502) for more
+  details.
+
+- `wrapHelm` exposes `passthru.pluginsDir` now which can be passed to
+  `helmfile`. For convenience, a top-level package `helmfile-wrapped` has been
+  added, which inherits `passthru.pluginsDir` from `kubernetes-helm-wrapped`.
+  Refer to [PR #217768](https://github.com/NixOS/nixpkgs/issues/217768) for
+  more details.
+
+- The `boot.initrd.network.udhcp.enable` option allows control over DHCP during
+  Stage 1 regardless of what `networking.useDHCP` is set to.
+
+- `networking.nftables` has the option `networking.nftables.table.<table>` now. This creates tables
   and have them be updated atomically, instead of flushing the ruleset.
 
-- `networking.nftables` is no longer flushing all rulesets on every reload.
-  Use `networking.nftables.flushRuleset = true;` to get back the old behaviour.
+- `hardware.nvidia` gained `datacenter` options for enabling NVIDIA Data Center
+  drivers and configuration of NVLink/NVSwitch topologies through
+  `nv-fabricmanager`.
 
-## Nixpkgs internals {#sec-release-23.11-nixpkgs-internals}
+- The new `boot.bcache.enable` option allows completely removing `bcache`
+  mount support. It is enabled by default.
 
-- The use of `sourceRoot = "source";`, `sourceRoot = "source/subdir";`, and similar lines in package derivations using the default `unpackPhase` is deprecated as it requires `unpackPhase` to always produce a directory named "source". Use `sourceRoot = src.name`, `sourceRoot = "${src.name}/subdir";`, or `setSourceRoot = "sourceRoot=$(echo */subdir)";` or similar instead.
+- `security.sudo` provides two extra options now, while not changing the
+  module's default behaviour:
+  - `defaultOptions` controls the options used for the default rules;
+  - `keepTerminfo` controls whether `TERMINFO` and `TERMINFO_DIRS` are preserved
+    for `root` and the `wheel` group.
 
-- The `django` alias in the python package set was upgraded to Django 4.x.
-  Applications that consume Django should always pin their python environment
-  to a compatible major version, so they can move at their own pace.
+- `virtualisation.googleComputeImage` provides a `efi` option to support UEFI
+  booting now.
 
-  ```nix
-  python = python3.override {
-    packageOverrides = self: super: {
-      django = super.django_3;
+- CoreDNS may be built with external plugins now. This may be done by
+  overriding `externalPlugins` and `vendorHash` arguments like this:
+
+  ```
+  services.coredns = {
+    enable = true;
+    package = pkgs.coredns.override {
+      externalPlugins = [
+        {name = "fanout"; repo = "github.com/networkservicemesh/fanout"; version = "v1.9.1";}
+      ];
+      vendorHash = "<SRI hash>";
     };
   };
   ```
 
-- The `qemu-vm.nix` module by default now identifies block devices via
-  persistent names available in `/dev/disk/by-*`. Because the rootDevice is
-  identfied by its filesystem label, it needs to be formatted before the VM is
-  started. The functionality of automatically formatting the rootDevice in the
-  initrd is removed from the QEMU module. However, for tests that depend on
-  this functionality, a test utility for the scripted initrd is added
-  (`nixos/tests/common/auto-format-root-device.nix`). To use this in a NixOS
-  test, import the module, e.g. `imports = [
-  ./common/auto-format-root-device.nix ];` When you use the systemd initrd, you
-  can automatically format the root device by setting
-  `virtualisation.fileSystems."/".autoFormat = true;`.
+  To get the necessary SRI hash, set `vendorHash = "";`. The build will fail
+  and produce the correct `vendorHash` in the error message.
+
+  If you use this feature, updates to CoreDNS may require updating `vendorHash`
+  by following these steps again.
+
+- Using `fusuma` enables the following plugins now:
+  [appmatcher](https://github.com/iberianpig/fusuma-plugin-appmatcher),
+  [keypress](https://github.com/iberianpig/fusuma-plugin-keypress),
+  [sendkey](https://github.com/iberianpig/fusuma-plugin-sendkey),
+  [tap](https://github.com/iberianpig/fusuma-plugin-tap) and
+  [wmctrl](https://github.com/iberianpig/fusuma-plugin-wmctrl).
+
+- The Home Assistant module offers support for installing custom components and
+  lovelace modules now. Available at
+  [`services.home-assistant.customComponents`](#opt-services.home-assistant.customComponents)
+  and
+  [`services.home-assistant.customLovelaceModules`](#opt-services.home-assistant.customLovelaceModules).
+
+- TeX Live environments can now be built with the new `texlive.withPackages`.
+  The procedure for creating custom TeX packages has been changed. Refer to the
+  [Nixpkgs
+  manual](https://nixos.org/manual/nixpkgs/stable/#sec-language-texlive-custom-packages)
+  for more details.
+
+- In `wxGTK32`, the webkit module `wxWebView` has been enabled on all builds.
+  Prior releases only enabled this on Darwin.
+
+- Support for WiFi6 (IEEE 802.11ax) and WPA3-SAE-PK was enabled in the
+  `hostapd` package, along with a significant rework of the hostapd module.
+
+- LXD supports virtual machine instances now to complement the existing
+  container support.
+
+- The `nixos-rebuild` command has been given a `list-generations` subcommand.
+  Refer to `man nixos-rebuild` for more details.
+
+- [`sudo-rs`], a reimplementation of `sudo` in Rust, is now supported.
+  An experimental new module `security.sudo-rs` was added.
+  Switching to it (via ` security.sudo-rs.enable = true;`) introduces
+  slight changes in sudo behaviour, due to `sudo-rs`' current limitations:
+  - terminfo-related environment variables aren't preserved for `root` and `wheel`;
+  - `root` and `wheel` are not given the ability to set (or preserve)
+    arbitrary environment variables.
+
+  **Note:** The `sudo-rs` module only takes configuration through `security.sudo-rs`,
+  and in particular does not automatically use previously-set rules; this could be
+  achieved with `security.sudo-rs.extraRules = security.sudo.extraRules;` for instance.
+
+[`sudo-rs`]: https://github.com/memorysafety/sudo-rs/
+
+- There is a new NixOS option when writing NixOS tests
+  `testing.initrdBackdoor`, that enables `backdoor.service` in initrd. Requires
+  `boot.initrd.systemd.enable` to be enabled. Boot will pause in Stage 1 at
+  `initrd.target`, and will listen for commands from the `Machine` python
+  interface, just like Stage 2 normally does. This enables commands to be sent
+  to test and debug Stage 1. Use `machine.switch_root()` to leave Stage 1 and
+  proceed to Stage 2.
+
+- The Linux kernel module `msr` (refer to
+  [`msr(4)`](https://man7.org/linux/man-pages/man4/msr.4.html)), which provides
+  an interface to read and write the model-specific registers (MSRs) of an x86
+  CPU, can now be configured via `hardware.cpu.x86.msr`.
+
+- The `qemu-vm.nix` module now supports disabling overriding `fileSystems` with
+  `virtualisation.fileSystems`. This enables the user to boot VMs from
+  "external" disk images not created by the qemu-vm module. You can stop the
+  qemu-vm module from overriding `fileSystems` by setting
+  `virtualisation.fileSystems = lib.mkForce { };`.
+
+- When using [split parity files](https://www.snapraid.it/manual#7.1) in `snapraid`,
+  the snapraid-sync systemd service will no longer fail to run.
+
+- `wpa_supplicant`'s configuration file cannot be read by non-root users, and
+  secrets (such as Pre-Shared Keys) can safely be passed via
+  `networking.wireless.environmentFile`.
+
+  The configuration file could previously be read, when `userControlled.enable` (non-default),
+  by users who are in both `wheel` and `userControlled.group` (defaults to `wheel`)
+
+
+## Nixpkgs Library {#sec-release-23.11-nixpkgs-lib}
+
+### Breaking Changes {#sec-release-23.11-lib-breaking}
+
+- [`lib.lists.foldl'`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.foldl-prime)
+  now always evaluates the initial accumulator argument first. If you depend on
+  the lazier behavior, consider using
+  [`lib.lists.foldl`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.foldl)
+  or
+  [`builtins.foldl'`](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-foldl')
+  instead.
+- [`lib.attrsets.foldlAttrs`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.attrsets.foldlAttrs)
+  now always evaluates the initial accumulator argument first.
+- Now that the internal NixOS transition to Markdown documentation is complete,
+  `lib.options.literalDocBook` has been removed after deprecation in 22.11.
+- `lib.types.string` is now fully deprecated and gives a warning when used.
+
+### Additions and Improvements {#sec-release-23.11-lib-additions-improvements}
+
+- [`lib.fileset`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-fileset):
+  A new sub-library to select local files to use for sources, designed to be
+  easy and safe to use.
+
+  This aims to be a replacement for `lib.sources`-based filtering. To learn
+  more about it, see [the blog
+  post](https://www.tweag.io/blog/2023-11-28-file-sets/) or [the
+  tutorial](https://nix.dev/tutorials/file-sets).
+
+- [`lib.gvariant`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-gvariant):
+  A partial and basic implementation of GVariant formatted strings. See
+  [GVariant Format
+  Strings](https://docs.gtk.org/glib/gvariant-format-strings.html) for details.
+
+  :::{.warning}
+  This API is not considered fully stable and it might therefore
+  change in backwards incompatible ways without prior notice.
+  :::
+
+- [`lib.asserts`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-asserts):
+  New function:
+  [`assertEachOneOf`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.asserts.assertEachOneOf).
+- [`lib.attrsets`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-attrsets):
+  New function:
+  [`attrsToList`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.attrsets.attrsToList).
+- [`lib.customisation`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-customisation):
+  New function:
+  [`makeScopeWithSplicing'`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.customisation.makeScopeWithSplicing-prime).
+- [`lib.fixedPoints`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-fixedPoints):
+  Documentation improvements for
+  [`lib.fixedPoints.fix`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.fixedPoints.fix).
+- `lib.generators`: New functions:
+  [`mkDconfKeyValue`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.generators.mkDconfKeyValue),
+  [`toDconfINI`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.generators.toDconfINI).
+
+  `lib.generators.toKeyValue` now supports the `indent` attribute in its first
+  argument.
+- [`lib.lists`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-lists):
+  New functions:
+  [`findFirstIndex`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.findFirstIndex),
+  [`hasPrefix`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.hasPrefix),
+  [`removePrefix`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.removePrefix),
+  [`commonPrefix`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.commonPrefix),
+  [`allUnique`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.allUnique).
+
+  Documentation improvements for
+  [`lib.lists.foldl'`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.lists.foldl-prime).
+- [`lib.meta`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-meta):
+  Documentation of functions now gets rendered
+- [`lib.path`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-path):
+  New functions:
+  [`hasPrefix`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.path.hasPrefix),
+  [`removePrefix`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.path.removePrefix),
+  [`splitRoot`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.path.splitRoot),
+  [`subpath.components`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.path.subpath.components).
+- [`lib.strings`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-strings):
+  New functions:
+  [`replicate`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.strings.replicate),
+  [`cmakeOptionType`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.strings.cmakeOptionType),
+  [`cmakeBool`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.strings.cmakeBool),
+  [`cmakeFeature`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.strings.cmakeFeature).
+- [`lib.trivial`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-trivial):
+  New function:
+  [`mirrorFunctionArgs`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.trivial.mirrorFunctionArgs).
+- `lib.systems`: New function:
+  [`equals`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.systems.equals).
+- [`lib.options`](https://nixos.org/manual/nixpkgs/stable#sec-functions-library-options):
+  Improved documentation for
+  [`mkPackageOption`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.options.mkPackageOption).
+
+  [`mkPackageOption`](https://nixos.org/manual/nixpkgs/stable#function-library-lib.options.mkPackageOption).
+  now also supports the `pkgsText` attribute.
+
+Module system:
+- Options in the `options` module argument now have the `declarationPositions`
+  attribute containing the position where the option was declared:
+  ```
+  $ nix-repl -f '<nixpkgs/nixos>' [...]
+  nix-repl> :p options.environment.systemPackages.declarationPositions
+  [ {
+    column = 7;
+    file = "/nix/store/vm9zf9wvfd628cchj0hdij1g4hzjrcz9-source/nixos/modules/config/system-path.nix";
+    line = 62;
+  } ]
+  ```
+
+  Not to be confused with `definitionsWithLocations`, which is the same but for option _definitions_.
+- Improved error message for option declarations missing `mkOption`
+
+### Deprecations {#sec-release-23.11-lib-deprecations}
+
+- `lib.meta.getExe pkg` (also available as `lib.getExe`) now gives a warning if
+  `pkg.meta.mainProgram` is not set, but it continues to default to the
+  derivation name. Nixpkgs accepts PRs that set `meta.mainProgram` on packages
+  where it makes sense. Use `lib.getExe' pkg "some-command"` to avoid the
+  warning and/or select a different executable.
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
new file mode 100644
index 0000000000000..6956c1ab6053e
--- /dev/null
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -0,0 +1,260 @@
+# Release 24.05 (“Uakari”, 2024.05/??) {#sec-release-24.05}
+
+Support is planned until the end of December 2024, handing over to 24.11.
+
+## Highlights {#sec-release-24.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. -->
+
+- `screen`'s module has been cleaned, and will now require you to set `programs.screen.enable` in order to populate `screenrc` and add the program to the environment.
+
+- `linuxPackages_testing_bcachefs` is now fully deprecated by `linuxPackages_testing`, and is therefore no longer available.
+
+- NixOS now installs a stub ELF loader that prints an informative error message when users attempt to run binaries not made for NixOS.
+   - This can be disabled through the `environment.stub-ld.enable` option.
+   - If you use `programs.nix-ld.enable`, no changes are needed. The stub will be disabled automatically.
+
+- Julia environments can now be built with arbitrary packages from the ecosystem using the `.withPackages` function. For example: `julia.withPackages ["Plots"]`.
+
+- A new option `systemd.sysusers.enable` was added. If enabled, users and
+  groups are created with systemd-sysusers instead of with a custom perl script.
+
+- A new option `system.etc.overlay.enable` was added. If enabled, `/etc` is
+  mounted via an overlayfs instead of being created by a custom perl script.
+
+- It is now possible to have a completely perlless system (i.e. a system
+  without perl). Previously, the NixOS activation depended on two perl scripts
+  which can now be replaced via an opt-in mechanism. To make your system
+  perlless, you can use the new perlless profile:
+  ```
+  { modulesPath, ... }: {
+    imports = [ "${modulesPath}/profiles/perlless.nix" ];
+  }
+  ```
+
+## New Services {#sec-release-24.05-new-services}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- [Guix](https://guix.gnu.org), a functional package manager inspired by Nix. Available as [services.guix](#opt-services.guix.enable).
+
+- [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable).
+
+- systemd's gateway, upload, and remote services, which provides ways of sending journals across the network. Enable using [services.journald.gateway](#opt-services.journald.gateway.enable), [services.journald.upload](#opt-services.journald.upload.enable), and [services.journald.remote](#opt-services.journald.remote.enable).
+
+- [GNS3](https://www.gns3.com/), a network software emulator. Available as [services.gns3-server](#opt-services.gns3-server.enable).
+
+- [rspamd-trainer](https://gitlab.com/onlime/rspamd-trainer), script triggered by a helper which reads mails from a specific mail inbox and feeds them into rspamd for spam/ham training.
+
+- [ollama](https://ollama.ai), server for running large language models locally.
+
+- [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable).
+The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been marked deprecated and will be dropped after 24.05 due to lack of maintenance of the anki-sync-server softwares.
+
+- [Suwayomi Server](https://github.com/Suwayomi/Suwayomi-Server), a free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org). Available as [services.suwayomi-server](#opt-services.suwayomi-server.enable).
+
+- [ping_exporter](https://github.com/czerwonk/ping_exporter), a Prometheus exporter for ICMP echo requests. Available as [services.prometheus.exporters.ping](#opt-services.prometheus.exporters.ping.enable).
+
+- [Clevis](https://github.com/latchset/clevis), a pluggable framework for automated decryption, used to unlock encrypted devices in initrd. Available as [boot.initrd.clevis.enable](#opt-boot.initrd.clevis.enable).
+
+- [TuxClocker](https://github.com/Lurkki14/tuxclocker), a hardware control and monitoring program. Available as [programs.tuxclocker](#opt-programs.tuxclocker.enable).
+
+## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- `himalaya` was updated to v1.0.0-beta, which introduces breaking changes. Check out the [release note](https://github.com/soywod/himalaya/releases/tag/v1.0.0-beta) for details.
+
+- The `power.ups` module now generates `upsd.conf`, `upsd.users` and `upsmon.conf` automatically from a set of new configuration options. This breaks compatibility with existing `power.ups` setups where these files were created manually. Back up these files before upgrading NixOS.
+
+- `k9s` was updated to v0.31. There have been various breaking changes in the config file format,
+  check out the changelog of [v0.29](https://github.com/derailed/k9s/releases/tag/v0.29.0),
+  [v0.30](https://github.com/derailed/k9s/releases/tag/v0.30.0) and
+  [v0.31](https://github.com/derailed/k9s/releases/tag/v0.31.0) for details. It is recommended
+  to back up your current configuration and let k9s recreate the new base configuration.
+
+- `idris2` was updated to v0.7.0. This version introduces breaking changes. Check out the [changelog](https://github.com/idris-lang/Idris2/blob/v0.7.0/CHANGELOG.md#v070) for details.
+
+- `nitter` requires a `guest_accounts.jsonl` to be provided as a path or loaded into the default location at `/var/lib/nitter/guest_accounts.jsonl`. See [Guest Account Branch Deployment](https://github.com/zedeus/nitter/wiki/Guest-Account-Branch-Deployment) for details.
+
+- Invidious has changed its default database username from `kemal` to `invidious`. Setups involving an externally provisioned database (i.e. `services.invidious.database.createLocally == false`) should adjust their configuration accordingly. The old `kemal` user will not be removed automatically even when the database is provisioned automatically.(https://github.com/NixOS/nixpkgs/pull/265857)
+
+- `inetutils` now has a lower priority to avoid shadowing the commonly used `util-linux`. If one wishes to restore the default priority, simply use `lib.setPrio 5 inetutils` or override with `meta.priority = 5`.
+
+- `paperless`' `services.paperless.extraConfig` setting has been removed and converted to the freeform type and option named `services.paperless.settings`.
+
+- The legacy and long deprecated systemd target `network-interfaces.target` has been removed. Use `network.target` instead.
+
+- `services.frp.settings` now generates the frp configuration file in TOML format as [recommended by upstream](https://github.com/fatedier/frp#configuration-files), instead of the legacy INI format. This has also introduced other changes in the configuration file structure and options.
+  - The `settings.common` section in the configuration is no longer valid and all the options form inside it now goes directly under `settings`.
+  - The `_` separating words in the configuration options is removed so the options are now in camel case. For example: `server_addr` becomes `serverAddr`, `server_port` becomes `serverPort` etc.
+  - Proxies are now defined with a new option `settings.proxies` which takes a list of proxies.
+  - Consult the [upstream documentation](https://github.com/fatedier/frp#example-usage) for more details on the changes.
+
+- `mkosi` was updated to v20. Parts of the user interface have changed. Consult the
+  release notes of [v19](https://github.com/systemd/mkosi/releases/tag/v19) and
+  [v20](https://github.com/systemd/mkosi/releases/tag/v20) for a list of changes.
+
+- `services.nginx` will no longer advertise HTTP/3 availability automatically. This must now be manually added, preferably to each location block.
+  Example:
+
+  ```nix
+    locations."/".extraConfig = ''
+      add_header Alt-Svc 'h3=":$server_port"; ma=86400';
+    '';
+    locations."^~ /assets/".extraConfig = ''
+      add_header Alt-Svc 'h3=":$server_port"; ma=86400';
+    '';
+
+  ```
+- The `kanata` package has been updated to v1.5.0, which includes [breaking changes](https://github.com/jtroo/kanata/releases/tag/v1.5.0).
+
+- The `craftos-pc` package has been updated to v2.8, which includes [breaking changes](https://github.com/MCJack123/craftos2/releases/tag/v2.8).
+  - Files are now handled in binary mode; this could break programs with embedded UTF-8 characters.
+  - The ROM was updated to match ComputerCraft version v1.109.2.
+  - The bundled Lua was updated to Lua v5.2, which includes breaking changes. See the [Lua manual](https://www.lua.org/manual/5.2/manual.html#8) for more information.
+  - The WebSocket API [was rewritten](https://github.com/MCJack123/craftos2/issues/337), which introduced breaking changes.
+
+- The latest available version of Nextcloud is v28 (available as `pkgs.nextcloud28`). The installation logic is as follows:
+  - If [`services.nextcloud.package`](#opt-services.nextcloud.package) is specified explicitly, this package will be installed (**recommended**)
+  - If [`system.stateVersion`](#opt-system.stateVersion) is >=24.05, `pkgs.nextcloud28` will be installed by default.
+  - If [`system.stateVersion`](#opt-system.stateVersion) is >=23.11, `pkgs.nextcloud27` will be installed by default.
+  - Please note that an upgrade from v26 (or older) to v28 directly is not possible. Please upgrade to `nextcloud27` (or earlier) first. Nextcloud prohibits skipping major versions while upgrading. You can upgrade by declaring [`services.nextcloud.package = pkgs.nextcloud27;`](options.html#opt-services.nextcloud.package).
+
+- The vendored third party libraries have been mostly removed from `cudaPackages.nsight_systems`, which we now only ship for `cudaPackages_11_8` and later due to outdated dependencies. Users comfortable with the vendored dependencies may use `overrideAttrs` to amend the `postPatch` phase and the `meta.broken` correspondingly. Alternatively, one could package the deprecated `boost170` locally, as required for `cudaPackages_11_4.nsight_systems`.
+
+- The `cudaPackages` package scope has been updated to `cudaPackages_12`.
+
+- `services.resolved.fallbackDns` can now be used to disable the upstream fallback servers entirely by setting it to an empty list. To get the previous behaviour of the upstream defaults set it to null, the new default, instead.
+
+- `services.avahi.nssmdns` got split into `services.avahi.nssmdns4` and `services.avahi.nssmdns6` which enable the mDNS NSS switch for IPv4 and IPv6 respectively.
+  Since most mDNS responders only register IPv4 addresses, most users want to keep the IPv6 support disabled to avoid long timeouts.
+
+- `multi-user.target` no longer depends on `network-online.target`.
+  This will potentially break services that assumed this was the case in the past.
+  This was changed for consistency with other distributions as well as improved boot times.
+
+  We have added a warning for services that are
+  `after = [ "network-online.target" ]` but do not depend on it (e.g. using `wants`).
+
+- `services.archisteamfarm` no longer uses the abbreviation `asf` for its state directory (`/var/lib/asf`), user and group (both `asf`). Instead the long name `archisteamfarm` is used.
+  Configurations with `system.stateVersion` 23.11 or earlier, default to the old stateDirectory until the 24.11 release and must either set the option explicitly or move the data to the new directory.
+
+- `networking.iproute2.enable` now does not set `environment.etc."iproute2/rt_tables".text`.
+
+  Setting `environment.etc."iproute2/{CONFIG_FILE_NAME}".text` will override the whole configuration file instead of appending it to the upstream configuration file.
+
+  `CONFIG_FILE_NAME` includes `bpf_pinning`, `ematch_map`, `group`, `nl_protos`, `rt_dsfield`, `rt_protos`, `rt_realms`, `rt_scopes`, and `rt_tables`.
+
+- `netbox` was updated to v3.7. `services.netbox.package` still defaults
+  to v3.6 if `stateVersion` is earlier than 24.05. Refer to upstream's breaking
+  changes [for
+  v3.7.0](https://github.com/netbox-community/netbox/releases/tag/v3.7.0) and
+  upgrade NetBox by changing `services.netbox.package`. Database migrations
+  will be run automatically.
+
+- The executable file names for `firefox-devedition`, `firefox-beta`, `firefox-esr` now matches their package names, which is consistent with the `firefox-*-bin` packages. The desktop entries are also updated so that you can have multiple editions of firefox in your app launcher.
+
+- switch-to-configuration does not directly call systemd-tmpfiles anymore.
+  Instead, the new artificial sysinit-reactivation.target is introduced which
+  allows to restart multiple services that are ordered before sysinit.target
+  and respect the ordering between the services.
+
+- The `systemd.oomd` module behavior is changed as:
+
+  - Raise ManagedOOMMemoryPressureLimit from 50% to 80%. This should make systemd-oomd kill things less often, and fix issues like [this](https://pagure.io/fedora-workstation/issue/358).
+    Reference: [commit](https://src.fedoraproject.org/rpms/systemd/c/806c95e1c70af18f81d499b24cd7acfa4c36ffd6?branch=806c95e1c70af18f81d499b24cd7acfa4c36ffd6)
+
+  - Remove swap policy. This helps prevent killing processes when user's swap is small.
+
+  - Expand the memory pressure policy to system.slice, user-.slice, and all user owned slices. Reference: [commit](https://src.fedoraproject.org/rpms/systemd/c/7665e1796f915dedbf8e014f0a78f4f576d609bb)
+
+  - `systemd.oomd.enableUserServices` is renamed to `systemd.oomd.enableUserSlices`.
+
+- `security.pam.enableSSHAgentAuth` now requires `services.openssh.authorizedKeysFiles` to be non-empty,
+  which is the case when `services.openssh.enable` is true. Previously, `pam_ssh_agent_auth` silently failed to work.
+
+- The configuration format for `services.prometheus.exporters.snmp` changed with release 0.23.0.
+  The module now includes an optional config check, that is enabled by default, to make the change obvious before any deployment.
+  More information about the configuration syntax change is available in the [upstream repository](https://github.com/prometheus/snmp_exporter/blob/b75fc6b839ee3f3ccbee68bee55f1ae99555084a/auth-split-migration.md).
+
+- [watchdogd](https://troglobit.com/projects/watchdogd/), a system and process supervisor using watchdog timers. Available as [services.watchdogd](#opt-services.watchdogd.enable).
+
+## Other Notable Changes {#sec-release-24.05-notable-changes}
+
+<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
+
+- `addDriverRunpath` has been added to facilitate the deprecation of the old `addOpenGLRunpath` setuphook. This change is motivated by the evolution of the setuphook to include all hardware acceleration.
+
+- Cinnamon has been updated to 6.0. Please beware that the [Wayland session](https://blog.linuxmint.com/?p=4591) is still experimental in this release.
+
+- `services.postgresql.extraPlugins` changed its type from just a list of packages to also a function that returns such a list.
+  For example a config line like ``services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [ postgis ];`` is recommended to be changed to ``services.postgresql.extraPlugins = ps: with ps; [ postgis ];``;
+
+- Programs written in [Nim](https://nim-lang.org/) are built with libraries selected by lockfiles.
+  The `nimPackages` and `nim2Packages` sets have been removed.
+  See https://nixos.org/manual/nixpkgs/unstable#nim for more information.
+
+- [Portunus](https://github.com/majewsky/portunus) has been updated to major version 2.
+  This version of Portunus supports strong password hashes, but the legacy hash SHA-256 is also still supported to ensure a smooth migration of existing user accounts.
+  After upgrading, follow the instructions on the [upstream release notes](https://github.com/majewsky/portunus/releases/tag/v2.0.0) to upgrade all user accounts to strong password hashes.
+  Support for weak password hashes will be removed in NixOS 24.11.
+
+- `libass` now uses the native CoreText backend on Darwin, which may fix subtitle rendering issues with `mpv`, `ffmpeg`, etc.
+
+- [Lilypond](https://lilypond.org/index.html) and [Denemo](https://www.denemo.org) are now compiled with Guile 3.0.
+
+- The following options of the Nextcloud module were moved into [`services.nextcloud.extraOptions`](#opt-services.nextcloud.extraOptions) and renamed to match the name from Nextcloud's `config.php`:
+  - `logLevel` -> [`loglevel`](#opt-services.nextcloud.extraOptions.loglevel),
+  - `logType` -> [`log_type`](#opt-services.nextcloud.extraOptions.log_type),
+  - `defaultPhoneRegion` -> [`default_phone_region`](#opt-services.nextcloud.extraOptions.default_phone_region),
+  - `overwriteProtocol` -> [`overwriteprotocol`](#opt-services.nextcloud.extraOptions.overwriteprotocol),
+  - `skeletonDirectory` -> [`skeletondirectory`](#opt-services.nextcloud.extraOptions.skeletondirectory),
+  - `globalProfiles` -> [`profile.enabled`](#opt-services.nextcloud.extraOptions._profile.enabled_),
+  - `extraTrustedDomains` -> [`trusted_domains`](#opt-services.nextcloud.extraOptions.trusted_domains) and
+  - `trustedProxies` -> [`trusted_proxies`](#opt-services.nextcloud.extraOptions.trusted_proxies).
+
+- The option [`services.nextcloud.config.dbport`] of the Nextcloud module was removed to match upstream.
+  The port can be specified in [`services.nextcloud.config.dbhost`](#opt-services.nextcloud.config.dbhost).
+
+- The Yama LSM is now enabled by default in the kernel, which prevents ptracing
+  non-child processes. This means you will not be able to attach gdb to an
+  existing process, but will need to start that process from gdb (so it is a
+  child). Or you can set `boot.kernel.sysctl."kernel.yama.ptrace_scope"` to 0.
+
+- [Nginx virtual hosts](#opt-services.nginx.virtualHosts) using `forceSSL` or
+  `globalRedirect` can now have redirect codes other than 301 through
+  `redirectCode`.
+
+- The source of the `mockgen` package has changed to the [go.uber.org/mock](https://github.com/uber-go/mock) fork because [the original repository is no longer maintained](https://github.com/golang/mock#gomock).
+
+- `security.pam.enableSSHAgentAuth` was renamed to `security.pam.sshAgentAuth.enable` and an `authorizedKeysFiles`
+  option was added, to control which `authorized_keys` files are trusted.  It defaults to the previous behaviour,
+  **which is insecure**: see [#31611](https://github.com/NixOS/nixpkgs/issues/31611).
+
+- [](#opt-boot.kernel.sysctl._net.core.wmem_max_) changed from a string to an integer because of the addition of a custom merge option (taking the highest value defined to avoid conflicts between 2 services trying to set that value), just as [](#opt-boot.kernel.sysctl._net.core.rmem_max_) since 22.11.
+
+- `services.zfs.zed.enableMail` now uses the global `sendmail` wrapper defined by an email module
+  (such as msmtp or Postfix). It no longer requires using a special ZFS build with email support.
+
+- `nextcloud-setup.service` no longer changes the group of each file & directory inside `/var/lib/nextcloud/{config,data,store-apps}` if one of these directories has the wrong owner group. This was part of transitioning the group used for `/var/lib/nextcloud`, but isn't necessary anymore.
+
+- The `krb5` module has been rewritten and moved to `security.krb5`, moving all options but `security.krb5.enable` and `security.krb5.package` into `security.krb5.settings`.
+
+- Gitea 1.21 upgrade has several breaking changes, including:
+  - Custom themes and other assets that were previously stored in `custom/public/*` now belong in `custom/public/assets/*`
+  - New instances of Gitea using MySQL now ignore the `[database].CHARSET` config option and always use the `utf8mb4` charset, existing instances should migrate via the `gitea doctor convert` CLI command.
+
+- The `hardware.pulseaudio` module now sets permission of pulse user home directory to 755 when running in "systemWide" mode. It fixes [issue 114399](https://github.com/NixOS/nixpkgs/issues/114399).
+
+- The `btrbk` module now automatically selects and provides required compression
+  program depending on the configured `stream_compress` option. Since this
+  replaces the need for the `extraPackages` option, this option will be
+  deprecated in future releases.
+
+- The `mpich` package expression now requires `withPm` to be a list, e.g. `"hydra:gforker"` becomes `[ "hydra" "gforker" ]`.
+
+- QtMultimedia has changed its default backend to `QT_MEDIA_BACKEND=ffmpeg` (previously `gstreamer` on Linux or `darwin` on MacOS).
+  The previous native backends remain available but are now minimally maintained. Refer to [upstream documentation](https://doc.qt.io/qt-6/qtmultimedia-index.html#ffmpeg-as-the-default-backend) for further details about each platform.
diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index 81a5ea1750dec..8bab3752073ff 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -34,9 +34,6 @@ evalConfigArgs@
                  in lib.optional (e != "") (import e)
 }:
 
-let pkgs_ = pkgs;
-in
-
 let
   inherit (lib) optional;
 
@@ -58,8 +55,9 @@ let
         nixpkgs.system = lib.mkDefault system;
       })
       ++
-      (optional (pkgs_ != null) {
-        _module.args.pkgs = lib.mkForce pkgs_;
+      (optional (pkgs != null) {
+        # This should be default priority, so it conflicts with any user-defined pkgs.
+        nixpkgs.pkgs = pkgs;
       })
     );
   };
@@ -109,10 +107,11 @@ let
 
   nixosWithUserModules = noUserModules.extendModules { modules = allUserModules; };
 
-  withExtraArgs = nixosSystem: nixosSystem // {
+  withExtraAttrs = configuration: configuration // {
     inherit extraArgs;
-    inherit (nixosSystem._module.args) pkgs;
-    extendModules = args: withExtraArgs (nixosSystem.extendModules args);
+    inherit (configuration._module.args) pkgs;
+    inherit lib;
+    extendModules = args: withExtraAttrs (configuration.extendModules args);
   };
 in
-withWarnings (withExtraArgs nixosWithUserModules)
+withWarnings (withExtraAttrs nixosWithUserModules)
diff --git a/nixos/lib/make-btrfs-fs.nix b/nixos/lib/make-btrfs-fs.nix
new file mode 100644
index 0000000000000..277ff6a4dca84
--- /dev/null
+++ b/nixos/lib/make-btrfs-fs.nix
@@ -0,0 +1,67 @@
+# Builds an btrfs image containing a populated /nix/store with the closure
+# of store paths passed in the storePaths parameter, in addition to the
+# contents of a directory that can be populated with commands. The
+# generated image is sized to only fit its contents, with the expectation
+# that a script resizes the filesystem at boot time.
+{ pkgs
+, lib
+# List of derivations to be included
+, storePaths
+# Whether or not to compress the resulting image with zstd
+, compressImage ? false, zstd
+# Shell commands to populate the ./files directory.
+# All files in that directory are copied to the root of the FS.
+, populateImageCommands ? ""
+, volumeLabel
+, uuid ? "44444444-4444-4444-8888-888888888888"
+, btrfs-progs
+, libfaketime
+, fakeroot
+}:
+
+let
+  sdClosureInfo = pkgs.buildPackages.closureInfo { rootPaths = storePaths; };
+in
+pkgs.stdenv.mkDerivation {
+  name = "btrfs-fs.img${lib.optionalString compressImage ".zst"}";
+
+  nativeBuildInputs = [ btrfs-progs libfaketime fakeroot ] ++ lib.optional compressImage zstd;
+
+  buildCommand =
+    ''
+      ${if compressImage then "img=temp.img" else "img=$out"}
+
+      set -x
+      (
+          mkdir -p ./files
+          ${populateImageCommands}
+      )
+
+      mkdir -p ./rootImage/nix/store
+
+      xargs -I % cp -a --reflink=auto % -t ./rootImage/nix/store/ < ${sdClosureInfo}/store-paths
+      (
+        GLOBIGNORE=".:.."
+        shopt -u dotglob
+
+        for f in ./files/*; do
+            cp -a --reflink=auto -t ./rootImage/ "$f"
+        done
+      )
+
+      cp ${sdClosureInfo}/registration ./rootImage/nix-path-registration
+
+      touch $img
+      faketime -f "1970-01-01 00:00:01" fakeroot mkfs.btrfs -L ${volumeLabel} -U ${uuid} -r ./rootImage --shrink $img
+
+      if ! btrfs check $img; then
+        echo "--- 'btrfs check' failed for BTRFS image ---"
+        return 1
+      fi
+
+      if [ ${builtins.toString compressImage} ]; then
+        echo "Compressing image"
+        zstd -v --no-progress ./$img -o $out
+      fi
+    '';
+}
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index e5d82f4de7c9d..1a33abd01ea18 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -522,11 +522,16 @@ let format' = format; in let
     chmod 0644 $efiVars
   '';
 
+  createHydraBuildProducts = ''
+    mkdir -p $out/nix-support
+    echo "file ${format}-image $out/${filename}" >> $out/nix-support/hydra-build-products
+  '';
+
   buildImage = pkgs.vmTools.runInLinuxVM (
     pkgs.runCommand name {
       preVM = prepareImage + lib.optionalString touchEFIVars createEFIVars;
       buildInputs = with pkgs; [ util-linux e2fsprogs dosfstools ];
-      postVM = moveOrConvertImage + postVM;
+      postVM = moveOrConvertImage + createHydraBuildProducts + postVM;
       QEMU_OPTS =
         concatStringsSep " " (lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
         ++ lib.optionals touchEFIVars [
@@ -616,5 +621,5 @@ let format' = format; in let
 in
   if onlyNixStore then
     pkgs.runCommand name {}
-      (prepareImage + moveOrConvertImage + postVM)
+      (prepareImage + moveOrConvertImage + createHydraBuildProducts + postVM)
   else buildImage
diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix
index 99515b5b8276e..284934a7608ef 100644
--- a/nixos/lib/make-options-doc/default.nix
+++ b/nixos/lib/make-options-doc/default.nix
@@ -120,7 +120,7 @@ in rec {
     { meta.description = "List of NixOS options in JSON format";
       nativeBuildInputs = [
         pkgs.brotli
-        pkgs.python3Minimal
+        pkgs.python3
       ];
       options = builtins.toFile "options.json"
         (builtins.unsafeDiscardStringContext (builtins.toJSON optionsNix));
diff --git a/nixos/lib/make-single-disk-zfs-image.nix b/nixos/lib/make-single-disk-zfs-image.nix
index a3564f9a8b68e..585fa93b7fa0f 100644
--- a/nixos/lib/make-single-disk-zfs-image.nix
+++ b/nixos/lib/make-single-disk-zfs-image.nix
@@ -21,6 +21,9 @@
 , # size of the FAT partition, in megabytes.
   bootSize ? 1024
 
+  , # memory allocated for virtualized build instance
+  memSize ? 1024
+
 , # The size of the root partition, in megabytes.
   rootSize ? 2048
 
@@ -230,7 +233,7 @@ let
   ).runInLinuxVM (
     pkgs.runCommand name
       {
-        memSize = 1024;
+        inherit memSize;
         QEMU_OPTS = "-drive file=$rootDiskImage,if=virtio,cache=unsafe,werror=report";
         preVM = ''
           PATH=$PATH:${pkgs.qemu_kvm}/bin
diff --git a/nixos/lib/make-squashfs.nix b/nixos/lib/make-squashfs.nix
index b7c7078b73b1b..f28e2c6715805 100644
--- a/nixos/lib/make-squashfs.nix
+++ b/nixos/lib/make-squashfs.nix
@@ -1,15 +1,23 @@
 { lib, stdenv, squashfsTools, closureInfo
 
+,  fileName ? "squashfs"
 , # The root directory of the squashfs filesystem is filled with the
   # closures of the Nix store paths listed here.
   storeContents ? []
+  # Pseudo files to be added to squashfs image
+, pseudoFiles ? []
+, noStrip ? false
 , # Compression parameters.
   # For zstd compression you can use "zstd -Xcompression-level 6".
   comp ? "xz -Xdict-size 100%"
 }:
 
+let
+  pseudoFilesArgs = lib.concatMapStrings (f: ''-p "${f}" '') pseudoFiles;
+  compFlag = if comp == null then "-no-compression" else "-comp ${comp}";
+in
 stdenv.mkDerivation {
-  name = "squashfs.img";
+  name = "${fileName}.img";
   __structuredAttrs = true;
 
   nativeBuildInputs = [ squashfsTools ];
@@ -31,8 +39,8 @@ stdenv.mkDerivation {
     '' + ''
 
       # Generate the squashfs image.
-      mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $out \
-        -no-hardlinks -keep-as-directory -all-root -b 1048576 -comp ${comp} \
+      mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $out ${pseudoFilesArgs} \
+        -no-hardlinks ${lib.optionalString noStrip "-no-strip"} -keep-as-directory -all-root -b 1048576 ${compFlag} \
         -processors $NIX_BUILD_CORES
     '';
 }
diff --git a/nixos/lib/qemu-common.nix b/nixos/lib/qemu-common.nix
index 4fff2e0a6f15e..b946f62d93dc3 100644
--- a/nixos/lib/qemu-common.nix
+++ b/nixos/lib/qemu-common.nix
@@ -40,6 +40,7 @@ rec {
       otherHostGuestMatrix = {
         aarch64-darwin = {
           aarch64-linux = "${qemuPkg}/bin/qemu-system-aarch64 -machine virt,gic-version=2,accel=hvf:tcg -cpu max";
+          inherit (otherHostGuestMatrix.x86_64-darwin) x86_64-linux;
         };
         x86_64-darwin = {
           x86_64-linux = "${qemuPkg}/bin/qemu-system-x86_64 -machine type=q35,accel=hvf:tcg -cpu max";
diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix
index 641b47def0397..347ee73039364 100644
--- a/nixos/lib/systemd-lib.nix
+++ b/nixos/lib/systemd-lib.nix
@@ -20,12 +20,16 @@ in rec {
       pkgs.runCommand "unit-${mkPathSafeName name}"
         { preferLocalBuild = true;
           allowSubstitutes = false;
-          inherit (unit) text;
+          # unit.text can be null. But variables that are null listed in
+          # passAsFile are ignored by nix, resulting in no file being created,
+          # making the mv operation fail.
+          text = optionalString (unit.text != null) unit.text;
+          passAsFile = [ "text" ];
         }
         ''
           name=${shellEscape name}
           mkdir -p "$out/$(dirname -- "$name")"
-          echo -n "$text" > "$out/$name"
+          mv "$textPath" "$out/$name"
         ''
     else
       pkgs.runCommand "unit-${mkPathSafeName name}-disabled"
@@ -80,6 +84,10 @@ in rec {
     optional (attr ? ${name} && !elem attr.${name} values)
       "Systemd ${group} field `${name}' cannot have value `${toString attr.${name}}'.";
 
+  assertValuesSomeOfOr = name: values: default: group: attr:
+    optional (attr ? ${name} && !(all (x: elem x values) (splitString " " attr.${name}) || attr.${name} == default))
+      "Systemd ${group} field `${name}' cannot have value `${toString attr.${name}}'.";
+
   assertHasField = name: group: attr:
     optional (!(attr ? ${name}))
       "Systemd ${group} field `${name}' must exist.";
@@ -274,7 +282,7 @@ in rec {
       });
     in "${out}/bin/${scriptName}";
 
-  unitConfig = { config, options, ... }: {
+  unitConfig = { config, name, options, ... }: {
     config = {
       unitConfig =
         optionalAttrs (config.requires != [])
@@ -294,9 +302,9 @@ in rec {
         // optionalAttrs (config.requisite != [])
           { Requisite = toString config.requisite; }
         // optionalAttrs (config ? restartTriggers && config.restartTriggers != [])
-          { X-Restart-Triggers = "${pkgs.writeText "X-Restart-Triggers" (toString config.restartTriggers)}"; }
+          { X-Restart-Triggers = "${pkgs.writeText "X-Restart-Triggers-${name}" (toString config.restartTriggers)}"; }
         // optionalAttrs (config ? reloadTriggers && config.reloadTriggers != [])
-          { X-Reload-Triggers = "${pkgs.writeText "X-Reload-Triggers" (toString config.reloadTriggers)}"; }
+          { X-Reload-Triggers = "${pkgs.writeText "X-Reload-Triggers-${name}" (toString config.reloadTriggers)}"; }
         // optionalAttrs (config.description != "") {
           Description = config.description; }
         // optionalAttrs (config.documentation != []) {
@@ -352,9 +360,13 @@ in rec {
     };
   };
 
-  commonUnitText = def: ''
+  commonUnitText = def: lines: ''
       [Unit]
       ${attrsToSection def.unitConfig}
+    '' + lines + lib.optionalString (def.wantedBy != [ ]) ''
+
+      [Install]
+      WantedBy=${concatStringsSep " " def.wantedBy}
     '';
 
   targetToUnit = name: def:
@@ -368,80 +380,73 @@ in rec {
 
   serviceToUnit = name: def:
     { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
-      text = commonUnitText def +
-        ''
-          [Service]
-          ${let env = cfg.globalEnvironment // def.environment;
-            in concatMapStrings (n:
-              let s = optionalString (env.${n} != null)
-                "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
-              # systemd max line length is now 1MiB
-              # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
-              in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)}
-          ${if def ? reloadIfChanged && def.reloadIfChanged then ''
-            X-ReloadIfChanged=true
-          '' else if (def ? restartIfChanged && !def.restartIfChanged) then ''
-            X-RestartIfChanged=false
-          '' else ""}
-          ${optionalString (def ? stopIfChanged && !def.stopIfChanged) "X-StopIfChanged=false"}
-          ${attrsToSection def.serviceConfig}
-        '';
+      text = commonUnitText def (''
+        [Service]
+      '' + (let env = cfg.globalEnvironment // def.environment;
+        in concatMapStrings (n:
+          let s = optionalString (env.${n} != null)
+            "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
+          # systemd max line length is now 1MiB
+          # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
+          in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env))
+      + (if def ? reloadIfChanged && def.reloadIfChanged then ''
+        X-ReloadIfChanged=true
+      '' else if (def ? restartIfChanged && !def.restartIfChanged) then ''
+        X-RestartIfChanged=false
+      '' else "")
+       + optionalString (def ? stopIfChanged && !def.stopIfChanged) ''
+         X-StopIfChanged=false
+      '' + attrsToSection def.serviceConfig);
     };
 
   socketToUnit = name: def:
     { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
-      text = commonUnitText def +
-        ''
-          [Socket]
-          ${attrsToSection def.socketConfig}
-          ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
-          ${concatStringsSep "\n" (map (s: "ListenDatagram=${s}") def.listenDatagrams)}
-        '';
+      text = commonUnitText def ''
+        [Socket]
+        ${attrsToSection def.socketConfig}
+        ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
+        ${concatStringsSep "\n" (map (s: "ListenDatagram=${s}") def.listenDatagrams)}
+      '';
     };
 
   timerToUnit = name: def:
     { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
-      text = commonUnitText def +
-        ''
-          [Timer]
-          ${attrsToSection def.timerConfig}
-        '';
+      text = commonUnitText def ''
+        [Timer]
+        ${attrsToSection def.timerConfig}
+      '';
     };
 
   pathToUnit = name: def:
     { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
-      text = commonUnitText def +
-        ''
-          [Path]
-          ${attrsToSection def.pathConfig}
-        '';
+      text = commonUnitText def ''
+        [Path]
+        ${attrsToSection def.pathConfig}
+      '';
     };
 
   mountToUnit = name: def:
     { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
-      text = commonUnitText def +
-        ''
-          [Mount]
-          ${attrsToSection def.mountConfig}
-        '';
+      text = commonUnitText def ''
+        [Mount]
+        ${attrsToSection def.mountConfig}
+      '';
     };
 
   automountToUnit = name: def:
     { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
-      text = commonUnitText def +
-        ''
-          [Automount]
-          ${attrsToSection def.automountConfig}
-        '';
+      text = commonUnitText def ''
+        [Automount]
+        ${attrsToSection def.automountConfig}
+      '';
     };
 
   sliceToUnit = name: def:
     { inherit (def) aliases wantedBy requiredBy enable overrideStrategy;
-      text = commonUnitText def +
-        ''
-          [Slice]
-          ${attrsToSection def.sliceConfig}
-        '';
+      text = commonUnitText def ''
+        [Slice]
+        ${attrsToSection def.sliceConfig}
+      '';
     };
 
   # Create a directory that contains systemd definition files from an attrset
diff --git a/nixos/lib/systemd-network-units.nix b/nixos/lib/systemd-network-units.nix
index 14ff0b3742eaa..1d5f823f3678d 100644
--- a/nixos/lib/systemd-network-units.nix
+++ b/nixos/lib/systemd-network-units.nix
@@ -23,6 +23,12 @@ in {
     '' + optionalString (def.vlanConfig != { }) ''
       [VLAN]
       ${attrsToSection def.vlanConfig}
+    '' + optionalString (def.ipvlanConfig != { }) ''
+      [IPVLAN]
+      ${attrsToSection def.ipvlanConfig}
+    '' + optionalString (def.ipvtapConfig != { }) ''
+      [IPVTAP]
+      ${attrsToSection def.ipvtapConfig}
     '' + optionalString (def.macvlanConfig != { }) ''
       [MACVLAN]
       ${attrsToSection def.macvlanConfig}
@@ -65,6 +71,9 @@ in {
     '' + optionalString (def.vrfConfig != { }) ''
       [VRF]
       ${attrsToSection def.vrfConfig}
+    '' + optionalString (def.wlanConfig != { }) ''
+      [WLAN]
+      ${attrsToSection def.wlanConfig}
     '' + optionalString (def.batmanAdvancedConfig != { }) ''
       [BatmanAdvanced]
       ${attrsToSection def.batmanAdvancedConfig}
diff --git a/nixos/lib/test-driver/default.nix b/nixos/lib/test-driver/default.nix
index 33313059fff77..1acdaacc4e658 100644
--- a/nixos/lib/test-driver/default.nix
+++ b/nixos/lib/test-driver/default.nix
@@ -4,19 +4,21 @@
 , qemu_pkg ? qemu_test
 , coreutils
 , imagemagick_light
-, libtiff
 , netpbm
 , qemu_test
 , socat
+, ruff
 , tesseract4
 , vde2
 , extraPythonPackages ? (_ : [])
+, nixosTests
 }:
 
-python3Packages.buildPythonApplication rec {
+python3Packages.buildPythonApplication {
   pname = "nixos-test-driver";
   version = "1.1";
   src = ./.;
+  pyproject = true;
 
   propagatedBuildInputs = [
     coreutils
@@ -30,15 +32,22 @@ python3Packages.buildPythonApplication rec {
     ++ (lib.optionals enableOCR [ imagemagick_light tesseract4 ])
     ++ extraPythonPackages python3Packages;
 
+  nativeBuildInputs = [
+    python3Packages.setuptools
+  ];
+
+  passthru.tests = {
+    inherit (nixosTests.nixos-test-driver) driver-timeout;
+  };
+
   doCheck = true;
-  nativeCheckInputs = with python3Packages; [ mypy pylint black ];
+  nativeCheckInputs = with python3Packages; [ mypy ruff 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
+    echo -e "\x1b[32m## run mypy\x1b[0m"
+    mypy test_driver extract-docstrings.py
+    echo -e "\x1b[32m## run ruff\x1b[0m"
+    ruff .
+    echo -e "\x1b[32m## run black\x1b[0m"
+    black --check --diff .
   '';
 }
diff --git a/nixos/lib/test-driver/extract-docstrings.py b/nixos/lib/test-driver/extract-docstrings.py
index 5aec4c89a9d74..64850ca711f3b 100644
--- a/nixos/lib/test-driver/extract-docstrings.py
+++ b/nixos/lib/test-driver/extract-docstrings.py
@@ -1,5 +1,6 @@
 import ast
 import sys
+from pathlib import Path
 
 """
 This program takes all the Machine class methods and prints its methods in
@@ -40,27 +41,34 @@ some_function(param1, param2)
 
 """
 
-assert len(sys.argv) == 2
 
-with open(sys.argv[1], "r") as f:
-    module = ast.parse(f.read())
+def main() -> None:
+    if len(sys.argv) != 2:
+        print(f"Usage: {sys.argv[0]} <path-to-test-driver>")
+        sys.exit(1)
 
-class_definitions = (node for node in module.body if isinstance(node, ast.ClassDef))
+    module = ast.parse(Path(sys.argv[1]).read_text())
 
-machine_class = next(filter(lambda x: x.name == "Machine", class_definitions))
-assert machine_class is not None
+    class_definitions = (node for node in module.body if isinstance(node, ast.ClassDef))
 
-function_definitions = [
-    node for node in machine_class.body if isinstance(node, ast.FunctionDef)
-]
-function_definitions.sort(key=lambda x: x.name)
+    machine_class = next(filter(lambda x: x.name == "Machine", class_definitions))
+    assert machine_class is not None
 
-for f in function_definitions:
-    docstr = ast.get_docstring(f)
-    if docstr is not None:
-        args = ", ".join((a.arg for a in f.args.args[1:]))
-        args = f"({args})"
+    function_definitions = [
+        node for node in machine_class.body if isinstance(node, ast.FunctionDef)
+    ]
+    function_definitions.sort(key=lambda x: x.name)
 
-        docstr = "\n".join((f"    {l}" for l in docstr.strip().splitlines()))
+    for function in function_definitions:
+        docstr = ast.get_docstring(function)
+        if docstr is not None:
+            args = ", ".join(a.arg for a in function.args.args[1:])
+            args = f"({args})"
 
-        print(f"{f.name}{args}\n\n:{docstr[1:]}\n")
+            docstr = "\n".join(f"    {line}" for line in docstr.strip().splitlines())
+
+            print(f"{function.name}{args}\n\n:{docstr[1:]}\n")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/nixos/lib/test-driver/pyproject.toml b/nixos/lib/test-driver/pyproject.toml
new file mode 100644
index 0000000000000..8638f14dfdaef
--- /dev/null
+++ b/nixos/lib/test-driver/pyproject.toml
@@ -0,0 +1,44 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "nixos-test-driver"
+version = "0.0.0"
+
+[project.scripts]
+nixos-test-driver = "test_driver:main"
+generate-driver-symbols = "test_driver:generate_driver_symbols"
+
+[tool.setuptools.packages]
+find = {}
+
+[tool.setuptools.package-data]
+test_driver = ["py.typed"]
+
+[tool.ruff]
+line-length = 88
+
+select = ["E", "F", "I", "U", "N"]
+ignore = ["E501"]
+
+# xxx: we can import https://pypi.org/project/types-colorama/ here
+[[tool.mypy.overrides]]
+module = "colorama.*"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "ptpython.*"
+ignore_missing_imports = true
+
+[tool.black]
+line-length = 88
+target-version = ['py39']
+include = '\.pyi?$'
+
+[tool.mypy]
+python_version = "3.10"
+warn_redundant_casts = true
+disallow_untyped_calls = true
+disallow_untyped_defs = true
+no_implicit_optional = true
diff --git a/nixos/lib/test-driver/setup.py b/nixos/lib/test-driver/setup.py
deleted file mode 100644
index 1719b988db689..0000000000000
--- a/nixos/lib/test-driver/setup.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from setuptools import setup, find_packages
-
-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",
-      "generate-driver-symbols=test_driver:generate_driver_symbols"
-    ]
-  },
-)
diff --git a/nixos/lib/test-driver/shell.nix b/nixos/lib/test-driver/shell.nix
new file mode 100644
index 0000000000000..367bbad556c03
--- /dev/null
+++ b/nixos/lib/test-driver/shell.nix
@@ -0,0 +1,2 @@
+with import ../../.. {};
+pkgs.callPackage ./default.nix {}
diff --git a/nixos/lib/test-driver/test_driver/__init__.py b/nixos/lib/test-driver/test_driver/__init__.py
index c90e3d9e1cdb6..9daae1e941a65 100755
--- a/nixos/lib/test-driver/test_driver/__init__.py
+++ b/nixos/lib/test-driver/test_driver/__init__.py
@@ -1,11 +1,12 @@
-from pathlib import Path
 import argparse
-import ptpython.repl
 import os
 import time
+from pathlib import Path
+
+import ptpython.repl
 
-from test_driver.logger import rootlog
 from test_driver.driver import Driver
+from test_driver.logger import rootlog
 
 
 class EnvDefault(argparse.Action):
@@ -25,9 +26,7 @@ class EnvDefault(argparse.Action):
                 )
         if required and default:
             required = False
-        super(EnvDefault, self).__init__(
-            default=default, required=required, nargs=nargs, **kwargs
-        )
+        super().__init__(default=default, required=required, nargs=nargs, **kwargs)
 
     def __call__(self, parser, namespace, values, option_string=None):  # type: ignore
         setattr(namespace, self.dest, values)
@@ -78,6 +77,14 @@ def main() -> None:
         help="vlans to span by the driver",
     )
     arg_parser.add_argument(
+        "--global-timeout",
+        type=int,
+        metavar="GLOBAL_TIMEOUT",
+        action=EnvDefault,
+        envvar="globalTimeout",
+        help="Timeout in seconds for the whole test",
+    )
+    arg_parser.add_argument(
         "-o",
         "--output_directory",
         help="""The path to the directory where outputs copied from the VM will be placed.
@@ -104,6 +111,7 @@ def main() -> None:
         args.testscript.read_text(),
         args.output_directory.resolve(),
         args.keep_vm_state,
+        args.global_timeout,
     ) as driver:
         if args.interactive:
             history_dir = os.getcwd()
diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py
index 835d60ec3b4fd..786821b0cc0d6 100644
--- a/nixos/lib/test-driver/test_driver/driver.py
+++ b/nixos/lib/test-driver/test_driver/driver.py
@@ -1,14 +1,16 @@
-from contextlib import contextmanager
-from pathlib import Path
-from typing import Any, Dict, Iterator, List, Union, Optional, Callable, ContextManager
 import os
 import re
+import signal
 import tempfile
+import threading
+from contextlib import contextmanager
+from pathlib import Path
+from typing import Any, Callable, ContextManager, Dict, Iterator, List, Optional, Union
 
 from test_driver.logger import rootlog
 from test_driver.machine import Machine, NixStartScript, retry
-from test_driver.vlan import VLan
 from test_driver.polling_condition import PollingCondition
+from test_driver.vlan import VLan
 
 
 def get_tmp_dir() -> Path:
@@ -41,6 +43,8 @@ class Driver:
     vlans: List[VLan]
     machines: List[Machine]
     polling_conditions: List[PollingCondition]
+    global_timeout: int
+    race_timer: threading.Timer
 
     def __init__(
         self,
@@ -49,9 +53,12 @@ class Driver:
         tests: str,
         out_dir: Path,
         keep_vm_state: bool = False,
+        global_timeout: int = 24 * 60 * 60 * 7,
     ):
         self.tests = tests
         self.out_dir = out_dir
+        self.global_timeout = global_timeout
+        self.race_timer = threading.Timer(global_timeout, self.terminate_test)
 
         tmp_dir = get_tmp_dir()
 
@@ -82,6 +89,7 @@ class Driver:
 
     def __exit__(self, *_: Any) -> None:
         with rootlog.nested("cleanup"):
+            self.race_timer.cancel()
             for machine in self.machines:
                 machine.release()
 
@@ -144,6 +152,10 @@ class Driver:
 
     def run_tests(self) -> None:
         """Run the test script (for non-interactive test runs)"""
+        rootlog.info(
+            f"Test will time out and terminate in {self.global_timeout} seconds"
+        )
+        self.race_timer.start()
         self.test_script()
         # TODO: Collect coverage data
         for machine in self.machines:
@@ -161,6 +173,19 @@ class Driver:
         with rootlog.nested("wait for all VMs to finish"):
             for machine in self.machines:
                 machine.wait_for_shutdown()
+            self.race_timer.cancel()
+
+    def terminate_test(self) -> None:
+        # This will be usually running in another thread than
+        # the thread actually executing the test script.
+        with rootlog.nested("timeout reached; test terminating..."):
+            for machine in self.machines:
+                machine.release()
+            # As we cannot `sys.exit` from another thread
+            # We can at least force the main thread to get SIGTERM'ed.
+            # This will prevent any user who caught all the exceptions
+            # to swallow them and prevent itself from terminating.
+            os.kill(os.getpid(), signal.SIGTERM)
 
     def create_machine(self, args: Dict[str, Any]) -> Machine:
         tmp_dir = get_tmp_dir()
diff --git a/nixos/lib/test-driver/test_driver/logger.py b/nixos/lib/test-driver/test_driver/logger.py
index e6182ff7c761d..116244b5e4ae0 100644
--- a/nixos/lib/test-driver/test_driver/logger.py
+++ b/nixos/lib/test-driver/test_driver/logger.py
@@ -1,13 +1,17 @@
-from colorama import Style, Fore
-from contextlib import contextmanager
-from typing import Any, Dict, Iterator
-from queue import Queue, Empty
-from xml.sax.saxutils import XMLGenerator
+# mypy: disable-error-code="no-untyped-call"
+# drop the above line when mypy is upgraded to include
+# https://github.com/python/typeshed/commit/49b717ca52bf0781a538b04c0d76a5513f7119b8
 import codecs
 import os
 import sys
 import time
 import unicodedata
+from contextlib import contextmanager
+from queue import Empty, Queue
+from typing import Any, Dict, Iterator
+from xml.sax.saxutils import XMLGenerator
+
+from colorama import Fore, Style
 
 
 class Logger:
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index 809fd690d7173..da60b669fa27e 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -1,7 +1,3 @@
-from contextlib import _GeneratorContextManager, nullcontext
-from pathlib import Path
-from queue import Queue
-from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
 import base64
 import io
 import os
@@ -16,9 +12,15 @@ import sys
 import tempfile
 import threading
 import time
+from contextlib import _GeneratorContextManager, nullcontext
+from pathlib import Path
+from queue import Queue
+from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
 
 from test_driver.logger import rootlog
 
+from .qmp import QMPSession
+
 CHAR_TO_KEY = {
     "A": "shift-a",
     "N": "shift-n",
@@ -144,6 +146,7 @@ class StartCommand:
     def cmd(
         self,
         monitor_socket_path: Path,
+        qmp_socket_path: Path,
         shell_socket_path: Path,
         allow_reboot: bool = False,
     ) -> str:
@@ -167,6 +170,7 @@ class StartCommand:
 
         return (
             f"{self._cmd}"
+            f" -qmp unix:{qmp_socket_path},server=on,wait=off"
             f" -monitor unix:{monitor_socket_path}"
             f" -chardev socket,id=shell,path={shell_socket_path}"
             f"{qemu_opts}"
@@ -194,11 +198,14 @@ class StartCommand:
         state_dir: Path,
         shared_dir: Path,
         monitor_socket_path: Path,
+        qmp_socket_path: Path,
         shell_socket_path: Path,
         allow_reboot: bool,
     ) -> subprocess.Popen:
         return subprocess.Popen(
-            self.cmd(monitor_socket_path, shell_socket_path, allow_reboot),
+            self.cmd(
+                monitor_socket_path, qmp_socket_path, shell_socket_path, allow_reboot
+            ),
             stdin=subprocess.PIPE,
             stdout=subprocess.PIPE,
             stderr=subprocess.STDOUT,
@@ -236,14 +243,14 @@ class LegacyStartCommand(StartCommand):
 
     def __init__(
         self,
-        netBackendArgs: Optional[str] = None,
-        netFrontendArgs: Optional[str] = None,
+        netBackendArgs: Optional[str] = None,  # noqa: N803
+        netFrontendArgs: Optional[str] = None,  # noqa: N803
         hda: Optional[Tuple[Path, str]] = None,
         cdrom: Optional[str] = None,
         usb: Optional[str] = None,
         bios: Optional[str] = None,
-        qemuBinary: Optional[str] = None,
-        qemuFlags: Optional[str] = None,
+        qemuBinary: Optional[str] = None,  # noqa: N803
+        qemuFlags: Optional[str] = None,  # noqa: N803
     ):
         if qemuBinary is not None:
             self._cmd = qemuBinary
@@ -309,6 +316,7 @@ class Machine:
     shared_dir: Path
     state_dir: Path
     monitor_path: Path
+    qmp_path: Path
     shell_path: Path
 
     start_command: StartCommand
@@ -317,6 +325,7 @@ class Machine:
     process: Optional[subprocess.Popen]
     pid: Optional[int]
     monitor: Optional[socket.socket]
+    qmp_client: Optional[QMPSession]
     shell: Optional[socket.socket]
     serial_thread: Optional[threading.Thread]
 
@@ -352,6 +361,7 @@ class Machine:
 
         self.state_dir = self.tmp_dir / f"vm-state-{self.name}"
         self.monitor_path = self.state_dir / "monitor"
+        self.qmp_path = self.state_dir / "qmp"
         self.shell_path = self.state_dir / "shell"
         if (not self.keep_vm_state) and self.state_dir.exists():
             self.cleanup_statedir()
@@ -360,6 +370,7 @@ class Machine:
         self.process = None
         self.pid = None
         self.monitor = None
+        self.qmp_client = None
         self.shell = None
         self.serial_thread = None
 
@@ -436,8 +447,7 @@ class Machine:
         """
 
         def check_active(_: Any) -> bool:
-            info = self.get_unit_info(unit, user)
-            state = info["ActiveState"]
+            state = self.get_unit_property(unit, "ActiveState", user)
             if state == "failed":
                 raise Exception(f'unit "{unit}" reached state "{state}"')
 
@@ -480,6 +490,35 @@ class Machine:
             if line_pattern.match(line)
         )
 
+    def get_unit_property(
+        self,
+        unit: str,
+        property: str,
+        user: Optional[str] = None,
+    ) -> str:
+        status, lines = self.systemctl(
+            f'--no-pager show "{unit}" --property="{property}"',
+            user,
+        )
+        if status != 0:
+            raise Exception(
+                f'retrieving systemctl property "{property}" for unit "{unit}"'
+                + ("" if user is None else f' under user "{user}"')
+                + f" failed with exit code {status}"
+            )
+
+        invalid_output_message = (
+            f'systemctl show --property "{property}" "{unit}"'
+            f"produced invalid output: {lines}"
+        )
+
+        line_pattern = re.compile(r"^([^=]+)=(.*)$")
+        match = line_pattern.match(lines)
+        assert match is not None, invalid_output_message
+
+        assert match[1] == property, invalid_output_message
+        return match[2]
+
     def systemctl(self, q: str, user: Optional[str] = None) -> Tuple[int, str]:
         """
         Runs `systemctl` commands with optional support for
@@ -599,7 +638,7 @@ class Machine:
             return (-1, output.decode())
 
         # Get the return code
-        self.shell.send("echo ${PIPESTATUS[0]}\n".encode())
+        self.shell.send(b"echo ${PIPESTATUS[0]}\n")
         rc = int(self._next_newline_closed_block_from_shell().strip())
 
         return (rc, output.decode(errors="replace"))
@@ -736,7 +775,7 @@ class Machine:
         )
         return output
 
-    def wait_until_tty_matches(self, tty: str, regexp: str) -> None:
+    def wait_until_tty_matches(self, tty: str, regexp: str, timeout: int = 900) -> None:
         """Wait until the visible output on the chosen TTY matches regular
         expression. Throws an exception on timeout.
         """
@@ -752,7 +791,7 @@ class Machine:
             return len(matcher.findall(text)) > 0
 
         with self.nested(f"waiting for {regexp} to appear on tty {tty}"):
-            retry(tty_matches)
+            retry(tty_matches, timeout)
 
     def send_chars(self, chars: str, delay: Optional[float] = 0.01) -> None:
         """
@@ -764,7 +803,7 @@ class Machine:
             for char in chars:
                 self.send_key(char, delay, log=False)
 
-    def wait_for_file(self, filename: str) -> None:
+    def wait_for_file(self, filename: str, timeout: int = 900) -> None:
         """
         Waits until the file exists in the machine's file system.
         """
@@ -774,9 +813,11 @@ class Machine:
             return status == 0
 
         with self.nested(f"waiting for file '{filename}'"):
-            retry(check_file)
+            retry(check_file, timeout)
 
-    def wait_for_open_port(self, port: int, addr: str = "localhost") -> None:
+    def wait_for_open_port(
+        self, port: int, addr: str = "localhost", timeout: int = 900
+    ) -> None:
         """
         Wait until a process is listening on the given TCP port and IP address
         (default `localhost`).
@@ -787,9 +828,33 @@ class Machine:
             return status == 0
 
         with self.nested(f"waiting for TCP port {port} on {addr}"):
-            retry(port_is_open)
+            retry(port_is_open, timeout)
 
-    def wait_for_closed_port(self, port: int, addr: str = "localhost") -> None:
+    def wait_for_open_unix_socket(
+        self, addr: str, is_datagram: bool = False, timeout: int = 900
+    ) -> None:
+        """
+        Wait until a process is listening on the given UNIX-domain socket
+        (default to a UNIX-domain stream socket).
+        """
+
+        nc_flags = [
+            "-z",
+            "-uU" if is_datagram else "-U",
+        ]
+
+        def socket_is_open(_: Any) -> bool:
+            status, _ = self.execute(f"nc {' '.join(nc_flags)} {addr}")
+            return status == 0
+
+        with self.nested(
+            f"waiting for UNIX-domain {'datagram' if is_datagram else 'stream'} on '{addr}'"
+        ):
+            retry(socket_is_open, timeout)
+
+    def wait_for_closed_port(
+        self, port: int, addr: str = "localhost", timeout: int = 900
+    ) -> None:
         """
         Wait until nobody is listening on the given TCP port and IP address
         (default `localhost`).
@@ -800,7 +865,7 @@ class Machine:
             return status != 0
 
         with self.nested(f"waiting for TCP port {port} on {addr} to be closed"):
-            retry(port_is_closed)
+            retry(port_is_closed, timeout)
 
     def start_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]:
         return self.systemctl(f"start {jobname}", user)
@@ -839,6 +904,9 @@ class Machine:
 
             while True:
                 chunk = self.shell.recv(1024)
+                # No need to print empty strings, it means we are waiting.
+                if len(chunk) == 0:
+                    continue
                 self.log(f"Guest shell says: {chunk!r}")
                 # NOTE: for this to work, nothing must be printed after this line!
                 if b"Spawning backdoor root shell..." in chunk:
@@ -974,7 +1042,7 @@ class Machine:
         """
         return self._get_screen_text_variants([2])[0]
 
-    def wait_for_text(self, regex: str) -> None:
+    def wait_for_text(self, regex: str, timeout: int = 900) -> None:
         """
         Wait until the supplied regular expressions matches the textual
         contents of the screen by using optical character recognition (see
@@ -997,7 +1065,7 @@ class Machine:
             return False
 
         with self.nested(f"waiting for {regex} to appear on screen"):
-            retry(screen_matches)
+            retry(screen_matches, timeout)
 
     def wait_for_console_text(self, regex: str, timeout: int | None = None) -> None:
         """
@@ -1083,11 +1151,13 @@ class Machine:
             self.state_dir,
             self.shared_dir,
             self.monitor_path,
+            self.qmp_path,
             self.shell_path,
             allow_reboot,
         )
         self.monitor, _ = monitor_socket.accept()
         self.shell, _ = shell_socket.accept()
+        self.qmp_client = QMPSession.from_path(self.qmp_path)
 
         # Store last serial console lines for use
         # of wait_for_console_text
@@ -1125,7 +1195,7 @@ class Machine:
             return
 
         assert self.shell
-        self.shell.send("poweroff\n".encode())
+        self.shell.send(b"poweroff\n")
         self.wait_for_shutdown()
 
     def crash(self) -> None:
@@ -1148,7 +1218,7 @@ class Machine:
         self.send_key("ctrl-alt-delete")
         self.connected = False
 
-    def wait_for_x(self) -> None:
+    def wait_for_x(self, timeout: int = 900) -> None:
         """
         Wait until it is possible to connect to the X server.
         """
@@ -1165,14 +1235,14 @@ class Machine:
             return status == 0
 
         with self.nested("waiting for the X11 server"):
-            retry(check_x)
+            retry(check_x, timeout)
 
     def get_window_names(self) -> List[str]:
         return self.succeed(
             r"xwininfo -root -tree | sed 's/.*0x[0-9a-f]* \"\([^\"]*\)\".*/\1/; t; d'"
         ).splitlines()
 
-    def wait_for_window(self, regexp: str) -> None:
+    def wait_for_window(self, regexp: str, timeout: int = 900) -> None:
         """
         Wait until an X11 window has appeared whose name matches the given
         regular expression, e.g., `wait_for_window("Terminal")`.
@@ -1190,7 +1260,7 @@ class Machine:
             return any(pattern.search(name) for name in names)
 
         with self.nested("waiting for a window to appear"):
-            retry(window_is_visible)
+            retry(window_is_visible, timeout)
 
     def sleep(self, secs: int) -> None:
         # We want to sleep in *guest* time, not *host* time.
@@ -1236,3 +1306,19 @@ class Machine:
     def run_callbacks(self) -> None:
         for callback in self.callbacks:
             callback()
+
+    def switch_root(self) -> None:
+        """
+        Transition from stage 1 to stage 2. This requires the
+        machine to be configured with `testing.initrdBackdoor = true`
+        and `boot.initrd.systemd.enable = true`.
+        """
+        self.wait_for_unit("initrd.target")
+        self.execute(
+            "systemctl isolate --no-block initrd-switch-root.target 2>/dev/null >/dev/null",
+            check_return=False,
+            check_output=False,
+        )
+        self.wait_for_console_text(r"systemd\[1\]:.*Switching root\.")
+        self.connected = False
+        self.connect()
diff --git a/nixos/lib/test-driver/test_driver/polling_condition.py b/nixos/lib/test-driver/test_driver/polling_condition.py
index 02ca0a03ab3dc..12cbad69e34e9 100644
--- a/nixos/lib/test-driver/test_driver/polling_condition.py
+++ b/nixos/lib/test-driver/test_driver/polling_condition.py
@@ -1,11 +1,11 @@
-from typing import Callable, Optional
-from math import isfinite
 import time
+from math import isfinite
+from typing import Callable, Optional
 
 from .logger import rootlog
 
 
-class PollingConditionFailed(Exception):
+class PollingConditionError(Exception):
     pass
 
 
@@ -60,7 +60,7 @@ class PollingCondition:
 
     def maybe_raise(self) -> None:
         if not self.check():
-            raise PollingConditionFailed(self.status_message(False))
+            raise PollingConditionError(self.status_message(False))
 
     def status_message(self, status: bool) -> str:
         return f"Polling condition {'succeeded' if status else 'failed'}: {self.description}"
diff --git a/nixos/lib/test-driver/test_driver/qmp.py b/nixos/lib/test-driver/test_driver/qmp.py
new file mode 100644
index 0000000000000..62ca6d7d5b802
--- /dev/null
+++ b/nixos/lib/test-driver/test_driver/qmp.py
@@ -0,0 +1,98 @@
+import json
+import logging
+import os
+import socket
+from collections.abc import Iterator
+from pathlib import Path
+from queue import Queue
+from typing import Any
+
+logger = logging.getLogger(__name__)
+
+
+class QMPAPIError(RuntimeError):
+    def __init__(self, message: dict[str, Any]):
+        assert "error" in message, "Not an error message!"
+        try:
+            self.class_name = message["class"]
+            self.description = message["desc"]
+            # NOTE: Some errors can occur before the Server is able to read the
+            # id member; in these cases the id member will not be part of the
+            # error response, even if provided by the client.
+            self.transaction_id = message.get("id")
+        except KeyError:
+            raise RuntimeError("Malformed QMP API error response")
+
+    def __str__(self) -> str:
+        return f"<QMP API error related to transaction {self.transaction_id} [{self.class_name}]: {self.description}>"
+
+
+class QMPSession:
+    def __init__(self, sock: socket.socket) -> None:
+        self.sock = sock
+        self.results: Queue[dict[str, str]] = Queue()
+        self.pending_events: Queue[dict[str, Any]] = Queue()
+        self.reader = sock.makefile("r")
+        self.writer = sock.makefile("w")
+        # Make the reader non-blocking so we can kind of select on it.
+        os.set_blocking(self.reader.fileno(), False)
+        hello = self._wait_for_new_result()
+        logger.debug(f"Got greeting from QMP API: {hello}")
+        # The greeting message format is:
+        # { "QMP": { "version": json-object, "capabilities": json-array } }
+        assert "QMP" in hello, f"Unexpected result: {hello}"
+        self.send("qmp_capabilities")
+
+    @classmethod
+    def from_path(cls, path: Path) -> "QMPSession":
+        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+        sock.connect(str(path))
+        return cls(sock)
+
+    def __del__(self) -> None:
+        self.sock.close()
+
+    def _wait_for_new_result(self) -> dict[str, str]:
+        assert self.results.empty(), "Results set is not empty, missed results!"
+        while self.results.empty():
+            self.read_pending_messages()
+        return self.results.get()
+
+    def read_pending_messages(self) -> None:
+        line = self.reader.readline()
+        if not line:
+            return
+        evt_or_result = json.loads(line)
+        logger.debug(f"Received a message: {evt_or_result}")
+
+        # It's a result
+        if "return" in evt_or_result or "QMP" in evt_or_result:
+            self.results.put(evt_or_result)
+        # It's an event
+        elif "event" in evt_or_result:
+            self.pending_events.put(evt_or_result)
+        else:
+            raise QMPAPIError(evt_or_result)
+
+    def wait_for_event(self, timeout: int = 10) -> dict[str, Any]:
+        while self.pending_events.empty():
+            self.read_pending_messages()
+
+        return self.pending_events.get(timeout=timeout)
+
+    def events(self, timeout: int = 10) -> Iterator[dict[str, Any]]:
+        while not self.pending_events.empty():
+            yield self.pending_events.get(timeout=timeout)
+
+    def send(self, cmd: str, args: dict[str, str] = {}) -> dict[str, str]:
+        self.read_pending_messages()
+        assert self.results.empty(), "Results set is not empty, missed results!"
+        data: dict[str, Any] = dict(execute=cmd)
+        if args != {}:
+            data["arguments"] = args
+
+        logger.debug(f"Sending {data} to QMP...")
+        json.dump(data, self.writer)
+        self.writer.write("\n")
+        self.writer.flush()
+        return self._wait_for_new_result()
diff --git a/nixos/lib/test-driver/test_driver/vlan.py b/nixos/lib/test-driver/test_driver/vlan.py
index f2a7f250d1d2f..ec9679108e58d 100644
--- a/nixos/lib/test-driver/test_driver/vlan.py
+++ b/nixos/lib/test-driver/test_driver/vlan.py
@@ -1,8 +1,8 @@
-from pathlib import Path
 import io
 import os
 import pty
 import subprocess
+from pathlib import Path
 
 from test_driver.logger import rootlog
 
diff --git a/nixos/lib/testing-python.nix b/nixos/lib/testing-python.nix
index 4904ad6e35913..f5222351518b5 100644
--- a/nixos/lib/testing-python.nix
+++ b/nixos/lib/testing-python.nix
@@ -42,6 +42,7 @@ rec {
     , nodes ? {}
     , testScript
     , enableOCR ? false
+    , globalTimeout ? (60 * 60)
     , name ? "unnamed"
     , skipTypeCheck ? false
       # Skip linting (mainly intended for faster dev cycles)
diff --git a/nixos/lib/testing/driver.nix b/nixos/lib/testing/driver.nix
index 23574698c0629..b6f01c38191d2 100644
--- a/nixos/lib/testing/driver.nix
+++ b/nixos/lib/testing/driver.nix
@@ -94,6 +94,7 @@ let
         wrapProgram $out/bin/nixos-test-driver \
           --set startScripts "''${vmStartScripts[*]}" \
           --set testScript "$out/test-script" \
+          --set globalTimeout "${toString config.globalTimeout}" \
           --set vlans '${toString vlans}' \
           ${lib.escapeShellArgs (lib.concatMap (arg: ["--add-flags" arg]) config.extraDriverArgs)}
       '';
@@ -123,6 +124,18 @@ in
       defaultText = "hostPkgs.qemu_test";
     };
 
+    globalTimeout = mkOption {
+      description = mdDoc ''
+        A global timeout for the complete test, expressed in seconds.
+        Beyond that timeout, every resource will be killed and released and the test will fail.
+
+        By default, we use a 1 hour timeout.
+      '';
+      type = types.int;
+      default = 60 * 60;
+      example = 10 * 60;
+    };
+
     enableOCR = mkOption {
       description = mdDoc ''
         Whether to enable Optical Character Recognition functionality for
@@ -175,7 +188,12 @@ in
   };
 
   config = {
-    _module.args.hostPkgs = config.hostPkgs;
+    _module.args = {
+      hostPkgs =
+        # Comment is in nixos/modules/misc/nixpkgs.nix
+        lib.mkOverride lib.modules.defaultOverridePriority
+          config.hostPkgs.__splicedPackages;
+    };
 
     driver = withChecks driver;
 
diff --git a/nixos/lib/testing/nodes.nix b/nixos/lib/testing/nodes.nix
index f58759b4cdba0..73e6d386fd1da 100644
--- a/nixos/lib/testing/nodes.nix
+++ b/nixos/lib/testing/nodes.nix
@@ -28,15 +28,14 @@ let
             {
               virtualisation.qemu.package = testModuleArgs.config.qemu.package;
             })
-          (optionalAttrs (!config.node.pkgsReadOnly) {
+          ({ options, ... }: {
             key = "nodes.nix-pkgs";
-            config = {
-              # Ensure we do not use aliases. Ideally this is only set
-              # when the test framework is used by Nixpkgs NixOS tests.
-              nixpkgs.config.allowAliases = false;
-              # TODO: switch to nixpkgs.hostPlatform and make sure containers-imperative test still evaluates.
-              nixpkgs.system = hostPkgs.stdenv.hostPlatform.system;
-            };
+            config = optionalAttrs (!config.node.pkgsReadOnly) (
+              mkIf (!options.nixpkgs.pkgs.isDefined) {
+                # TODO: switch to nixpkgs.hostPlatform and make sure containers-imperative test still evaluates.
+                nixpkgs.system = hostPkgs.stdenv.hostPlatform.system;
+              }
+            );
           })
           testModuleArgs.config.extraBaseModules
         ];
diff --git a/nixos/lib/testing/run.nix b/nixos/lib/testing/run.nix
index 0cd07d8afd21d..9440c1acdfd81 100644
--- a/nixos/lib/testing/run.nix
+++ b/nixos/lib/testing/run.nix
@@ -16,6 +16,15 @@ in
       '';
     };
 
+    rawTestDerivation = mkOption {
+      type = types.package;
+      description = mdDoc ''
+        Unfiltered version of `test`, for troubleshooting the test framework and `testBuildFailure` in the test framework's test suite.
+        This is not intended for general use. Use `test` instead.
+      '';
+      internal = true;
+    };
+
     test = mkOption {
       type = types.package;
       # TODO: can the interactive driver be configured to access the network?
@@ -29,25 +38,26 @@ in
   };
 
   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}";
+    rawTestDerivation = hostPkgs.stdenv.mkDerivation {
+      name = "vm-test-run-${config.name}";
 
-        requiredSystemFeatures = [ "kvm" "nixos-test" ];
+      requiredSystemFeatures = [ "kvm" "nixos-test" ];
 
-        buildCommand = ''
-          mkdir -p $out
+      buildCommand = ''
+        mkdir -p $out
 
-          # effectively mute the XMLLogger
-          export LOGFILE=/dev/null
+        # effectively mute the XMLLogger
+        export LOGFILE=/dev/null
 
-          ${config.driver}/bin/nixos-test-driver -o $out
-        '';
+        ${config.driver}/bin/nixos-test-driver -o $out
+      '';
 
-        passthru = config.passthru;
+      passthru = config.passthru;
 
-        meta = config.meta;
-      };
+      meta = config.meta;
+    };
+    test = lib.lazyDerivation { # lazyDerivation improves performance when only passthru items and/or meta are used.
+      derivation = config.rawTestDerivation;
       inherit (config) passthru meta;
     };
 
diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix
index 7ea9d6a5c7135..e618cf2f861a3 100644
--- a/nixos/lib/utils.nix
+++ b/nixos/lib/utils.nix
@@ -177,6 +177,7 @@ rec {
   genJqSecretsReplacementSnippet' = attr: set: output:
     let
       secrets = recursiveGetAttrWithJqPrefix set attr;
+      stringOrDefault = str: def: if str == "" then def else str;
     in ''
       if [[ -h '${output}' ]]; then
         rm '${output}'
@@ -195,10 +196,12 @@ rec {
                (attrNames secrets))
     + "\n"
     + "${pkgs.jq}/bin/jq >'${output}' "
-    + lib.escapeShellArg (concatStringsSep
-      " | "
-      (imap1 (index: name: ''${name} = $ENV.secret${toString index}'')
-             (attrNames secrets)))
+    + lib.escapeShellArg (stringOrDefault
+          (concatStringsSep
+            " | "
+            (imap1 (index: name: ''${name} = $ENV.secret${toString index}'')
+                   (attrNames secrets)))
+          ".")
     + ''
        <<'EOF'
       ${builtins.toJSON set}
diff --git a/nixos/maintainers/option-usages.nix b/nixos/maintainers/option-usages.nix
index 11247666ecda9..e9bafa21a58ae 100644
--- a/nixos/maintainers/option-usages.nix
+++ b/nixos/maintainers/option-usages.nix
@@ -9,17 +9,17 @@
 
 # This file is made to be used as follow:
 #
-#   $ nix-instantiate ./option-usage.nix --argstr testOption service.xserver.enable -A txtContent --eval
+#   $ nix-instantiate ./option-usages.nix --argstr testOption service.xserver.enable -A txtContent --eval
 #
 # or
 #
-#   $ nix-build ./option-usage.nix --argstr testOption service.xserver.enable -A txt -o service.xserver.enable._txt
+#   $ nix-build ./option-usages.nix --argstr testOption service.xserver.enable -A txt -o service.xserver.enable._txt
 #
 # Other targets exists such as `dotContent`, `dot`, and `pdf`.  If you are
 # looking for the option usage of multiple options, you can provide a list
 # as argument.
 #
-#   $ nix-build ./option-usage.nix --arg testOptions \
+#   $ nix-build ./option-usages.nix --arg testOptions \
 #      '["boot.loader.gummiboot.enable" "boot.loader.gummiboot.timeout"]' \
 #      -A txt -o gummiboot.list
 #
diff --git a/nixos/maintainers/scripts/azure-new/examples/basic/system.nix b/nixos/maintainers/scripts/azure-new/examples/basic/system.nix
index d283742701d19..d1044802e1f09 100644
--- a/nixos/maintainers/scripts/azure-new/examples/basic/system.nix
+++ b/nixos/maintainers/scripts/azure-new/examples/basic/system.nix
@@ -21,7 +21,6 @@ in
 
   virtualisation.azureImage.diskSize = 2500;
 
-  system.stateVersion = "20.03";
   boot.kernelPackages = pkgs.linuxPackages_latest;
 
   # test user doesn't have a password
diff --git a/nixos/maintainers/scripts/ec2/create-amis.sh b/nixos/maintainers/scripts/ec2/create-amis.sh
index 0c1656efaf1ca..d182c5c2a4794 100755
--- a/nixos/maintainers/scripts/ec2/create-amis.sh
+++ b/nixos/maintainers/scripts/ec2/create-amis.sh
@@ -27,31 +27,37 @@ var ${bucket:=nixos-amis}
 var ${service_role_name:=vmimport}
 
 # Output of the command:
-# > aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text | sort
+# $ nix-shell -I nixpkgs=. -p awscli --run 'aws ec2 describe-regions --region us-east-1 --all-regions --query "Regions[].{Name:RegionName}" --output text | sort | sed -e s/^/\ \ /'
 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
-         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
-     }
+  af-south-1
+  ap-east-1
+  ap-northeast-1
+  ap-northeast-2
+  ap-northeast-3
+  ap-south-1
+  ap-south-2
+  ap-southeast-1
+  ap-southeast-2
+  ap-southeast-3
+  ap-southeast-4
+  ca-central-1
+  eu-central-1
+  eu-central-2
+  eu-north-1
+  eu-south-1
+  eu-south-2
+  eu-west-1
+  eu-west-2
+  eu-west-3
+  il-central-1
+  me-central-1
+  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-container-image-inner.nix b/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
index 7b743d170bc64..ef00c6f86cbd6 100644
--- a/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
@@ -2,13 +2,13 @@
 # your system.  Help is available in the configuration.nix(5) man page
 # and in the NixOS manual (accessible by running ‘nixos-help’).
 
-{ config, pkgs, lib, ... }:
+{ config, pkgs, lib, modulesPath, ... }:
 
 {
   imports =
     [
       # Include the default lxd configuration.
-      ../../../modules/virtualisation/lxc-container.nix
+      "${modulesPath}/virtualisation/lxc-container.nix"
       # Include the container-specific autogenerated configuration.
       ./lxd.nix
     ];
@@ -16,5 +16,5 @@
   networking.useDHCP = false;
   networking.interfaces.eth0.useDHCP = true;
 
-  system.stateVersion = "21.05"; # Did you read the comment?
+  system.stateVersion = "@stateVersion@"; # Did you read the comment?
 }
diff --git a/nixos/maintainers/scripts/lxd/lxd-container-image.nix b/nixos/maintainers/scripts/lxd/lxd-container-image.nix
index 3bd1320b2b680..b77f9f5aabe09 100644
--- a/nixos/maintainers/scripts/lxd/lxd-container-image.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-container-image.nix
@@ -13,11 +13,15 @@
   };
 
   # copy the config for nixos-rebuild
-  system.activationScripts.config = ''
+  system.activationScripts.config = let
+    config = pkgs.substituteAll {
+      src = ./lxd-container-image-inner.nix;
+      stateVersion = lib.trivial.release;
+    };
+  in ''
     if [ ! -e /etc/nixos/configuration.nix ]; then
       mkdir -p /etc/nixos
-      cat ${./lxd-container-image-inner.nix} > /etc/nixos/configuration.nix
-      ${lib.getExe pkgs.gnused} 's|../../../modules/virtualisation/lxc-container.nix|<nixpkgs/nixos/modules/virtualisation/lxc-container.nix>|g' -i /etc/nixos/configuration.nix
+      cp ${config} /etc/nixos/configuration.nix
     fi
   '';
 
diff --git a/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix b/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix
index a8f2c63ac5c69..c1c50b32ff5bd 100644
--- a/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix
@@ -2,13 +2,13 @@
 # your system.  Help is available in the configuration.nix(5) man page
 # and in the NixOS manual (accessible by running ‘nixos-help’).
 
-{ config, pkgs, lib, ... }:
+{ config, pkgs, lib, modulesPath, ... }:
 
 {
   imports =
     [
       # Include the default lxd configuration.
-      ../../../modules/virtualisation/lxd-virtual-machine.nix
+      "${modulesPath}/virtualisation/lxd-virtual-machine.nix"
       # Include the container-specific autogenerated configuration.
       ./lxd.nix
     ];
@@ -16,5 +16,5 @@
   networking.useDHCP = false;
   networking.interfaces.eth0.useDHCP = true;
 
-  system.stateVersion = "23.05"; # Did you read the comment?
+  system.stateVersion = "@stateVersion@"; # Did you read the comment?
 }
diff --git a/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image.nix b/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image.nix
index eb0d9217d4021..0d96eea0e2d2c 100644
--- a/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image.nix
@@ -13,11 +13,15 @@
   };
 
   # copy the config for nixos-rebuild
-  system.activationScripts.config = ''
+  system.activationScripts.config = let
+    config = pkgs.substituteAll {
+      src = ./lxd-virtual-machine-image-inner.nix;
+      stateVersion = lib.trivial.release;
+    };
+  in ''
     if [ ! -e /etc/nixos/configuration.nix ]; then
       mkdir -p /etc/nixos
-      cat ${./lxd-virtual-machine-image-inner.nix} > /etc/nixos/configuration.nix
-      ${lib.getExe pkgs.gnused} 's|../../../modules/virtualisation/lxd-virtual-machine.nix|<nixpkgs/nixos/modules/virtualisation/lxd-virtual-machine.nix>|g' -i /etc/nixos/configuration.nix
+      cp ${config} /etc/nixos/configuration.nix
     fi
   '';
 
diff --git a/nixos/maintainers/scripts/oci/create-image.sh b/nixos/maintainers/scripts/oci/create-image.sh
new file mode 100755
index 0000000000000..0d7332a0b2720
--- /dev/null
+++ b/nixos/maintainers/scripts/oci/create-image.sh
@@ -0,0 +1,24 @@
+#! /usr/bin/env bash
+
+set -euo pipefail
+
+export NIX_PATH=nixpkgs=$(dirname $(readlink -f $0))/../../../..
+export NIXOS_CONFIG=$(dirname $(readlink -f $0))/../../../modules/virtualisation/oci-image.nix
+
+if (( $# < 1 )); then
+    (
+    echo "Usage: create-image.sh <architecture>"
+    echo
+    echo "Where <architecture> is one of:"
+    echo "  x86_64-linux"
+    echo "  aarch64-linux"
+    ) >&2
+fi
+
+system="$1"; shift
+
+nix-build '<nixpkgs/nixos>' \
+    -A config.system.build.OCIImage \
+    --argstr system "$system" \
+    --option system-features kvm \
+    -o oci-image
diff --git a/nixos/maintainers/scripts/oci/upload-image.sh b/nixos/maintainers/scripts/oci/upload-image.sh
new file mode 100755
index 0000000000000..e4870e94bf54c
--- /dev/null
+++ b/nixos/maintainers/scripts/oci/upload-image.sh
@@ -0,0 +1,100 @@
+#! /usr/bin/env bash
+
+set -euo pipefail
+
+script_dir="$(dirname $(readlink -f $0))"
+nixpkgs_root="$script_dir/../../../.."
+export NIX_PATH="nixpkgs=$nixpkgs_root"
+
+cat - <<EOF
+This script will locally build a NixOS image and upload it as a Custom Image
+using oci-cli. Make sure that an API key for the tenancy administrator has been
+added to '~/.oci'.
+For more info about configuring oci-cli, please visit
+https://docs.cloud.oracle.com/iaas/Content/API/Concepts/apisigningkey.htm#Required_Keys_and_OCIDs
+
+EOF
+
+qcow="oci-image/nixos.qcow2"
+if [ ! -f "$qcow" ]; then
+    echo "OCI image $qcow does not exist"
+    echo "Building image with create-image.sh for 'x86_64-linux'"
+    "$script_dir/create-image.sh" x86_64-linux
+    [ -f "$qcow" ] || { echo "Build failed: image not present after build"; exit 1; }
+else
+    echo "Using prebuilt image $qcow"
+fi
+
+cli="$(
+  nix-build '<nixpkgs>' \
+    --no-out-link \
+    -A oci-cli
+)"
+
+PATH="$cli/bin:$PATH"
+bucket="_TEMP_NIXOS_IMAGES_$RANDOM"
+
+echo "Creating a temporary bucket"
+root_ocid="$(
+  oci iam compartment list \
+  --all \
+  --compartment-id-in-subtree true \
+  --access-level ACCESSIBLE \
+  --include-root \
+  --raw-output \
+  --query "data[?contains(\"id\",'tenancy')].id | [0]"
+)"
+bucket_ocid=$(
+  oci os bucket create \
+    -c "$root_ocid" \
+    --name "$bucket" \
+    --raw-output \
+    --query "data.id"
+)
+# Clean up bucket on script termination
+trap 'echo Removing temporary bucket; oci os bucket delete --force --name "$bucket"' INT TERM EXIT
+
+echo "Uploading image to temporary bucket"
+oci os object put -bn "$bucket" --file "$qcow"
+
+echo "Importing image as a Custom Image"
+bucket_ns="$(oci os ns get --query "data" --raw-output)"
+image_id="$(
+  oci compute image import from-object \
+    -c "$root_ocid" \
+    --namespace "$bucket_ns" \
+    --bucket-name "$bucket" \
+    --name nixos.qcow2 \
+    --operating-system NixOS \
+    --source-image-type QCOW2 \
+    --launch-mode PARAVIRTUALIZED \
+    --display-name NixOS \
+    --raw-output \
+    --query "data.id"
+)"
+
+cat - <<EOF
+Image created! Please mark all available shapes as compatible with this image by
+visiting the following link and by selecting the 'Edit Details' button on:
+https://cloud.oracle.com/compute/images/$image_id
+EOF
+
+# Workaround until https://github.com/oracle/oci-cli/issues/399 is addressed
+echo "Sleeping for 15 minutes before cleaning up files in the temporary bucket"
+sleep $((15 * 60))
+
+echo "Deleting image from bucket"
+par_id="$(
+  oci os preauth-request list \
+    --bucket-name "$bucket" \
+    --raw-output \
+    --query "data[0].id"
+)"
+
+if [[ -n $par_id ]]; then
+  oci os preauth-request delete \
+    --bucket-name "$bucket" \
+    --par-id "$par_id"
+fi
+
+oci os object delete -bn "$bucket" --object-name nixos.qcow2 --force
diff --git a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
index 936dcee12949e..60f0535854dd5 100644
--- a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
+++ b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
@@ -20,6 +20,12 @@ in
       default = "nixos-openstack-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}";
     };
 
+    ramMB = mkOption {
+      type = types.int;
+      default = 1024;
+      description = lib.mdDoc "RAM allocation for build VM";
+    };
+
     sizeMB = mkOption {
       type = types.int;
       default = 8192;
@@ -64,7 +70,7 @@ in
       includeChannel = copyChannel;
 
       bootSize = 1000;
-
+      memSize = cfg.ramMB;
       rootSize = cfg.sizeMB;
       rootPoolProperties = {
         ashift = 12;
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
index 1e8bb78f302d6..0a931c6918f80 100644
--- a/nixos/modules/config/console.nix
+++ b/nixos/modules/config/console.nix
@@ -127,8 +127,8 @@ in
               ${optionalString (config.environment.sessionVariables ? XKB_CONFIG_ROOT)
                 "-I${config.environment.sessionVariables.XKB_CONFIG_ROOT}"
               } \
-              -model '${xkbModel}' -layout '${layout}' \
-              -option '${xkbOptions}' -variant '${xkbVariant}' > "$out"
+              -model '${xkb.model}' -layout '${xkb.layout}' \
+              -option '${xkb.options}' -variant '${xkb.variant}' > "$out"
           '');
     }
 
@@ -168,6 +168,9 @@ in
           # ...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.additionalUpstreamUnits = [
+          "systemd-vconsole-setup.service"
+        ];
         boot.initrd.systemd.storePaths = [
           "${config.boot.initrd.systemd.package}/lib/systemd/systemd-vconsole-setup"
           "${config.boot.initrd.systemd.package.kbd}/bin/setfont"
diff --git a/nixos/modules/config/fanout.nix b/nixos/modules/config/fanout.nix
new file mode 100644
index 0000000000000..60ee145f19af4
--- /dev/null
+++ b/nixos/modules/config/fanout.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.fanout;
+  mknodCmds = n: lib.lists.imap0 (i: s:
+    "mknod /dev/fanout${builtins.toString i} c $MAJOR ${builtins.toString i}"
+  ) (lib.lists.replicate n "");
+in
+{
+  options.services.fanout = {
+    enable = lib.mkEnableOption (lib.mdDoc "fanout");
+    fanoutDevices = lib.mkOption {
+      type = lib.types.int;
+      default = 1;
+      description = "Number of /dev/fanout devices";
+    };
+    bufferSize = lib.mkOption {
+      type = lib.types.int;
+      default = 16384;
+      description = "Size of /dev/fanout buffer in bytes";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    boot.extraModulePackages = [ config.boot.kernelPackages.fanout.out ];
+
+    boot.kernelModules = [ "fanout" ];
+
+    boot.extraModprobeConfig = ''
+      options fanout buffersize=${builtins.toString cfg.bufferSize}
+    '';
+
+    systemd.services.fanout = {
+      description = "Bring up /dev/fanout devices";
+      script = ''
+        MAJOR=$(${pkgs.gnugrep}/bin/grep fanout /proc/devices | ${pkgs.gawk}/bin/awk '{print $1}')
+        ${lib.strings.concatLines (mknodCmds cfg.fanoutDevices)}
+      '';
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "root";
+        RemainAfterExit = "yes";
+        Restart = "no";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/config/fonts/packages.nix b/nixos/modules/config/fonts/packages.nix
index 46907d5411ca5..37b705ecb345a 100644
--- a/nixos/modules/config/fonts/packages.nix
+++ b/nixos/modules/config/fonts/packages.nix
@@ -37,7 +37,7 @@ in
       gyre-fonts # TrueType substitutes for standard PostScript fonts
       liberation_ttf
       unifont
-      noto-fonts-emoji
+      noto-fonts-color-emoji
     ]);
   };
 }
diff --git a/nixos/modules/config/gnu.nix b/nixos/modules/config/gnu.nix
deleted file mode 100644
index a47d299b226b5..0000000000000
--- a/nixos/modules/config/gnu.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-{
-  options = {
-    gnu = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      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).
-      '';
-    };
-  };
-
-  config = lib.mkIf config.gnu {
-
-    environment.systemPackages = with pkgs;
-      # TODO: Adjust `requiredPackages' from `system-path.nix'.
-      # TODO: Add Inetutils once it has the new `ifconfig'.
-      [ parted
-        #fdisk  # XXX: GNU fdisk currently fails to build and it's redundant
-                # with the `parted' command.
-        nano zile
-        texinfo # for the stand-alone Info reader
-      ]
-      ++ lib.optional (!stdenv.isAarch32) grub2;
-
-
-    # GNU GRUB, where available.
-    boot.loader.grub.enable = !pkgs.stdenv.isAarch32;
-
-    # GNU lsh.
-    services.openssh.enable = false;
-    services.lshd.enable = true;
-    programs.ssh.startAgent = false;
-    services.xserver.startGnuPGAgent = true;
-
-    # TODO: GNU dico.
-    # TODO: GNU Inetutils' inetd.
-    # TODO: GNU Pies.
-  };
-}
diff --git a/nixos/modules/config/iproute2.nix b/nixos/modules/config/iproute2.nix
index 8f49e7dbf7de5..0cde57b759be3 100644
--- a/nixos/modules/config/iproute2.nix
+++ b/nixos/modules/config/iproute2.nix
@@ -7,7 +7,7 @@ let
 in
 {
   options.networking.iproute2 = {
-    enable = mkEnableOption (lib.mdDoc "copy IP route configuration files");
+    enable = mkEnableOption (lib.mdDoc "copying IP route configuration files");
     rttablesExtraConfig = mkOption {
       type = types.lines;
       default = "";
@@ -18,15 +18,9 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.etc."iproute2/bpf_pinning" = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/bpf_pinning"; };
-    environment.etc."iproute2/ematch_map"  = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/ematch_map";  };
-    environment.etc."iproute2/group"       = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/group";       };
-    environment.etc."iproute2/nl_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/nl_protos";   };
-    environment.etc."iproute2/rt_dsfield"  = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_dsfield";  };
-    environment.etc."iproute2/rt_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_protos";   };
-    environment.etc."iproute2/rt_realms"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_realms";   };
-    environment.etc."iproute2/rt_scopes"   = { mode = "0644"; text = fileContents "${pkgs.iproute2}/etc/iproute2/rt_scopes";   };
-    environment.etc."iproute2/rt_tables"   = { mode = "0644"; text = (fileContents "${pkgs.iproute2}/etc/iproute2/rt_tables")
-                                                                   + (optionalString (cfg.rttablesExtraConfig != "") "\n\n${cfg.rttablesExtraConfig}"); };
+    environment.etc."iproute2/rt_tables.d/nixos.conf" = {
+      mode = "0644";
+      text = cfg.rttablesExtraConfig;
+    };
   };
 }
diff --git a/nixos/modules/config/krb5/default.nix b/nixos/modules/config/krb5/default.nix
deleted file mode 100644
index df7a3f48236f0..0000000000000
--- a/nixos/modules/config/krb5/default.nix
+++ /dev/null
@@ -1,369 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.krb5;
-
-  # This is to provide support for old configuration options (as much as is
-  # reasonable). This can be removed after 18.03 was released.
-  defaultConfig = {
-    libdefaults = optionalAttrs (cfg.defaultRealm != null)
-      { default_realm = cfg.defaultRealm; };
-
-    realms = optionalAttrs (lib.all (value: value != null) [
-      cfg.defaultRealm cfg.kdc cfg.kerberosAdminServer
-    ]) {
-      ${cfg.defaultRealm} = {
-        kdc = cfg.kdc;
-        admin_server = cfg.kerberosAdminServer;
-      };
-    };
-
-    domain_realm = optionalAttrs (lib.all (value: value != null) [
-      cfg.domainRealm cfg.defaultRealm
-    ]) {
-      ".${cfg.domainRealm}" = cfg.defaultRealm;
-      ${cfg.domainRealm} = cfg.defaultRealm;
-    };
-  };
-
-  mergedConfig = (recursiveUpdate defaultConfig {
-    inherit (config.krb5)
-      kerberos libdefaults realms domain_realm capaths appdefaults plugins
-      extraConfig config;
-  });
-
-  filterEmbeddedMetadata = value: if isAttrs value then
-    (filterAttrs
-      (attrName: attrValue: attrName != "_module" && attrValue != null)
-        value)
-    else value;
-
-  indent = "  ";
-
-  mkRelation = name: value:
-    if (isList value) then
-      concatMapStringsSep "\n" (mkRelation name) value
-    else "${name} = ${mkVal value}";
-
-  mkVal = value:
-    if (value == true) then "true"
-    else if (value == false) then "false"
-    else if (isInt value) then (toString value)
-    else if (isAttrs value) then
-      let configLines = concatLists
-        (map (splitString "\n")
-          (mapAttrsToList mkRelation value));
-      in
-      (concatStringsSep "\n${indent}"
-        ([ "{" ] ++ configLines))
-      + "\n}"
-    else value;
-
-  mkMappedAttrsOrString = value: concatMapStringsSep "\n"
-    (line: if builtins.stringLength line > 0
-      then "${indent}${line}"
-      else line)
-    (splitString "\n"
-      (if isAttrs value then
-        concatStringsSep "\n"
-            (mapAttrsToList mkRelation value)
-        else value));
-
-in {
-
-  ###### interface
-
-  options = {
-    krb5 = {
-      enable = mkEnableOption (lib.mdDoc "building krb5.conf, configuration file for Kerberos V");
-
-      kerberos = mkOption {
-        type = types.package;
-        default = pkgs.krb5;
-        defaultText = literalExpression "pkgs.krb5";
-        example = literalExpression "pkgs.heimdal";
-        description = lib.mdDoc ''
-          The Kerberos implementation that will be present in
-          `environment.systemPackages` after enabling this
-          service.
-        '';
-      };
-
-      libdefaults = mkOption {
-        type = with types; either attrs lines;
-        default = {};
-        apply = attrs: filterEmbeddedMetadata attrs;
-        example = literalExpression ''
-          {
-            default_realm = "ATHENA.MIT.EDU";
-          };
-        '';
-        description = lib.mdDoc ''
-          Settings used by the Kerberos V5 library.
-        '';
-      };
-
-      realms = mkOption {
-        type = with types; either attrs lines;
-        default = {};
-        example = literalExpression ''
-          {
-            "ATHENA.MIT.EDU" = {
-              admin_server = "athena.mit.edu";
-              kdc = [
-                "athena01.mit.edu"
-                "athena02.mit.edu"
-              ];
-            };
-          };
-        '';
-        apply = attrs: filterEmbeddedMetadata attrs;
-        description = lib.mdDoc "Realm-specific contact information and settings.";
-      };
-
-      domain_realm = mkOption {
-        type = with types; either attrs lines;
-        default = {};
-        example = literalExpression ''
-          {
-            "example.com" = "EXAMPLE.COM";
-            ".example.com" = "EXAMPLE.COM";
-          };
-        '';
-        apply = attrs: filterEmbeddedMetadata attrs;
-        description = lib.mdDoc ''
-          Map of server hostnames to Kerberos realms.
-        '';
-      };
-
-      capaths = mkOption {
-        type = with types; either attrs lines;
-        default = {};
-        example = literalExpression ''
-          {
-            "ATHENA.MIT.EDU" = {
-              "EXAMPLE.COM" = ".";
-            };
-            "EXAMPLE.COM" = {
-              "ATHENA.MIT.EDU" = ".";
-            };
-          };
-        '';
-        apply = attrs: filterEmbeddedMetadata attrs;
-        description = lib.mdDoc ''
-          Authentication paths for non-hierarchical cross-realm authentication.
-        '';
-      };
-
-      appdefaults = mkOption {
-        type = with types; either attrs lines;
-        default = {};
-        example = literalExpression ''
-          {
-            pam = {
-              debug = false;
-              ticket_lifetime = 36000;
-              renew_lifetime = 36000;
-              max_timeout = 30;
-              timeout_shift = 2;
-              initial_timeout = 1;
-            };
-          };
-        '';
-        apply = attrs: filterEmbeddedMetadata attrs;
-        description = lib.mdDoc ''
-          Settings used by some Kerberos V5 applications.
-        '';
-      };
-
-      plugins = mkOption {
-        type = with types; either attrs lines;
-        default = {};
-        example = literalExpression ''
-          {
-            ccselect = {
-              disable = "k5identity";
-            };
-          };
-        '';
-        apply = attrs: filterEmbeddedMetadata attrs;
-        description = lib.mdDoc ''
-          Controls plugin module registration.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = with types; nullOr lines;
-        default = null;
-        example = ''
-          [logging]
-            kdc          = SYSLOG:NOTICE
-            admin_server = SYSLOG:NOTICE
-            default      = SYSLOG:NOTICE
-        '';
-        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.
-        '';
-      };
-
-      config = mkOption {
-        type = with types; nullOr lines;
-        default = null;
-        example = ''
-          [libdefaults]
-            default_realm = EXAMPLE.COM
-
-          [realms]
-            EXAMPLE.COM = {
-              admin_server = kerberos.example.com
-              kdc = kerberos.example.com
-              default_principal_flags = +preauth
-            }
-
-          [domain_realm]
-            example.com  = EXAMPLE.COM
-            .example.com = EXAMPLE.COM
-
-          [logging]
-            kdc          = SYSLOG:NOTICE
-            admin_server = SYSLOG:NOTICE
-            default      = SYSLOG:NOTICE
-        '';
-        description = lib.mdDoc ''
-          Verbatim `krb5.conf` configuration.  Note that this
-          is mutually exclusive with configuration via
-          `libdefaults`, `realms`,
-          `domain_realm`, `capaths`,
-          `appdefaults`, `plugins` and
-          `extraConfig` configuration options.  Consult
-          `man krb5.conf` for documentation.
-        '';
-      };
-
-      defaultRealm = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        example = "ATHENA.MIT.EDU";
-        description = lib.mdDoc ''
-          DEPRECATED, please use
-          `krb5.libdefaults.default_realm`.
-        '';
-      };
-
-      domainRealm = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        example = "athena.mit.edu";
-        description = lib.mdDoc ''
-          DEPRECATED, please create a map of server hostnames to Kerberos realms
-          in `krb5.domain_realm`.
-        '';
-      };
-
-      kdc = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        example = "kerberos.mit.edu";
-        description = lib.mdDoc ''
-          DEPRECATED, please pass a `kdc` attribute to a realm
-          in `krb5.realms`.
-        '';
-      };
-
-      kerberosAdminServer = mkOption {
-        type = with types; nullOr str;
-        default = null;
-        example = "kerberos.mit.edu";
-        description = lib.mdDoc ''
-          DEPRECATED, please pass an `admin_server` attribute
-          to a realm in `krb5.realms`.
-        '';
-      };
-    };
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    environment.systemPackages = [ cfg.kerberos ];
-
-    environment.etc."krb5.conf".text = if isString cfg.config
-      then cfg.config
-      else (''
-        [libdefaults]
-        ${mkMappedAttrsOrString mergedConfig.libdefaults}
-
-        [realms]
-        ${mkMappedAttrsOrString mergedConfig.realms}
-
-        [domain_realm]
-        ${mkMappedAttrsOrString mergedConfig.domain_realm}
-
-        [capaths]
-        ${mkMappedAttrsOrString mergedConfig.capaths}
-
-        [appdefaults]
-        ${mkMappedAttrsOrString mergedConfig.appdefaults}
-
-        [plugins]
-        ${mkMappedAttrsOrString mergedConfig.plugins}
-      '' + optionalString (mergedConfig.extraConfig != null)
-          ("\n" + mergedConfig.extraConfig));
-
-    warnings = flatten [
-      (optional (cfg.defaultRealm != null) ''
-        The option krb5.defaultRealm is deprecated, please use
-        krb5.libdefaults.default_realm.
-      '')
-      (optional (cfg.domainRealm != null) ''
-        The option krb5.domainRealm is deprecated, please use krb5.domain_realm.
-      '')
-      (optional (cfg.kdc != null) ''
-        The option krb5.kdc is deprecated, please pass a kdc attribute to a
-        realm in krb5.realms.
-      '')
-      (optional (cfg.kerberosAdminServer != null) ''
-        The option krb5.kerberosAdminServer is deprecated, please pass an
-        admin_server attribute to a realm in krb5.realms.
-      '')
-    ];
-
-    assertions = [
-      { assertion = !((builtins.any (value: value != null) [
-            cfg.defaultRealm cfg.domainRealm cfg.kdc cfg.kerberosAdminServer
-          ]) && ((builtins.any (value: value != {}) [
-              cfg.libdefaults cfg.realms cfg.domain_realm cfg.capaths
-              cfg.appdefaults cfg.plugins
-            ]) || (builtins.any (value: value != null) [
-              cfg.config cfg.extraConfig
-            ])));
-        message = ''
-          Configuration of krb5.conf by deprecated options is mutually exclusive
-          with configuration by section.  Please migrate your config using the
-          attributes suggested in the warnings.
-        '';
-      }
-      { assertion = !(cfg.config != null
-          && ((builtins.any (value: value != {}) [
-              cfg.libdefaults cfg.realms cfg.domain_realm cfg.capaths
-              cfg.appdefaults cfg.plugins
-            ]) || (builtins.any (value: value != null) [
-              cfg.extraConfig cfg.defaultRealm cfg.domainRealm cfg.kdc
-              cfg.kerberosAdminServer
-            ])));
-        message = ''
-          Configuration of krb5.conf using krb.config is mutually exclusive with
-          configuration by section.  If you want to mix the two, you can pass
-          lines to any configuration section or lines to krb5.extraConfig.
-        '';
-      }
-    ];
-  };
-}
diff --git a/nixos/modules/config/ldap.nix b/nixos/modules/config/ldap.nix
index d2f01fb87d32d..e374e4a7a27e9 100644
--- a/nixos/modules/config/ldap.nix
+++ b/nixos/modules/config/ldap.nix
@@ -226,18 +226,6 @@ in
       "ldap.conf" = ldapConfig;
     };
 
-    system.activationScripts = mkIf (!cfg.daemon.enable) {
-      ldap = stringAfter [ "etc" "groups" "users" ] ''
-        if test -f "${cfg.bind.passwordFile}" ; then
-          umask 0077
-          conf="$(mktemp)"
-          printf 'bindpw %s\n' "$(cat ${cfg.bind.passwordFile})" |
-          cat ${ldapConfig.source} - >"$conf"
-          mv -fT "$conf" /etc/ldap.conf
-        fi
-      '';
-    };
-
     system.nssModules = mkIf cfg.nsswitch (singleton (
       if cfg.daemon.enable then nss_pam_ldapd else nss_ldap
     ));
@@ -258,42 +246,63 @@ in
       };
     };
 
-    systemd.services = mkIf cfg.daemon.enable {
-      nslcd = {
-        wantedBy = [ "multi-user.target" ];
-
-        preStart = ''
-          umask 0077
-          conf="$(mktemp)"
-          {
-            cat ${nslcdConfig}
-            test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.passwordFile}' ||
-            printf 'bindpw %s\n' "$(cat '${cfg.bind.passwordFile}')"
-            test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpwFile}' ||
-            printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpwFile}')"
-          } >"$conf"
-          mv -fT "$conf" /run/nslcd/nslcd.conf
-        '';
-
-        restartTriggers = [
-          nslcdConfig
-          cfg.bind.passwordFile
-          cfg.daemon.rootpwmodpwFile
-        ];
-
-        serviceConfig = {
-          ExecStart = "${nslcdWrapped}/bin/nslcd";
-          Type = "forking";
-          Restart = "always";
-          User = "nslcd";
-          Group = "nslcd";
-          RuntimeDirectory = [ "nslcd" ];
-          PIDFile = "/run/nslcd/nslcd.pid";
-          AmbientCapabilities = "CAP_SYS_RESOURCE";
+    systemd.services = mkMerge [
+      (mkIf (!cfg.daemon.enable) {
+        ldap-password = {
+          wantedBy = [ "sysinit.target" ];
+          before = [ "sysinit.target" "shutdown.target" ];
+          conflicts = [ "shutdown.target" ];
+          unitConfig.DefaultDependencies = false;
+          serviceConfig.Type = "oneshot";
+          serviceConfig.RemainAfterExit = true;
+          script = ''
+            if test -f "${cfg.bind.passwordFile}" ; then
+              umask 0077
+              conf="$(mktemp)"
+              printf 'bindpw %s\n' "$(cat ${cfg.bind.passwordFile})" |
+              cat ${ldapConfig.source} - >"$conf"
+              mv -fT "$conf" /etc/ldap.conf
+            fi
+          '';
         };
-      };
+      })
+
+      (mkIf cfg.daemon.enable {
+        nslcd = {
+          wantedBy = [ "multi-user.target" ];
+
+          preStart = ''
+            umask 0077
+            conf="$(mktemp)"
+            {
+              cat ${nslcdConfig}
+              test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.passwordFile}' ||
+              printf 'bindpw %s\n' "$(cat '${cfg.bind.passwordFile}')"
+              test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpwFile}' ||
+              printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpwFile}')"
+            } >"$conf"
+            mv -fT "$conf" /run/nslcd/nslcd.conf
+          '';
 
-    };
+          restartTriggers = [
+            nslcdConfig
+            cfg.bind.passwordFile
+            cfg.daemon.rootpwmodpwFile
+          ];
+
+          serviceConfig = {
+            ExecStart = "${nslcdWrapped}/bin/nslcd";
+            Type = "forking";
+            Restart = "always";
+            User = "nslcd";
+            Group = "nslcd";
+            RuntimeDirectory = [ "nslcd" ];
+            PIDFile = "/run/nslcd/nslcd.pid";
+            AmbientCapabilities = "CAP_SYS_RESOURCE";
+          };
+        };
+      })
+    ];
 
   };
 
diff --git a/nixos/modules/config/ldso.nix b/nixos/modules/config/ldso.nix
new file mode 100644
index 0000000000000..72ae3958d8869
--- /dev/null
+++ b/nixos/modules/config/ldso.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) last splitString mkOption types mdDoc optionals;
+
+  libDir = pkgs.stdenv.hostPlatform.libDir;
+  ldsoBasename = builtins.unsafeDiscardStringContext (last (splitString "/" pkgs.stdenv.cc.bintools.dynamicLinker));
+
+  pkgs32 = pkgs.pkgsi686Linux;
+  libDir32 = pkgs32.stdenv.hostPlatform.libDir;
+  ldsoBasename32 = builtins.unsafeDiscardStringContext (last (splitString "/" pkgs32.stdenv.cc.bintools.dynamicLinker));
+in {
+  options = {
+    environment.ldso = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = mdDoc ''
+        The executable to link into the normal FHS location of the ELF loader.
+      '';
+    };
+
+    environment.ldso32 = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = mdDoc ''
+        The executable to link into the normal FHS location of the 32-bit ELF loader.
+
+        This currently only works on x86_64 architectures.
+      '';
+    };
+  };
+
+  config = {
+    assertions = [
+      { assertion = isNull config.environment.ldso32 || pkgs.stdenv.isx86_64;
+        message = "Option environment.ldso32 currently only works on x86_64.";
+      }
+    ];
+
+    systemd.tmpfiles.rules = (
+      if isNull config.environment.ldso then [
+        "r /${libDir}/${ldsoBasename} - - - - -"
+      ] else [
+        "d /${libDir} 0755 root root - -"
+        "L+ /${libDir}/${ldsoBasename} - - - - ${config.environment.ldso}"
+      ]
+    ) ++ optionals pkgs.stdenv.isx86_64 (
+      if isNull config.environment.ldso32 then [
+        "r /${libDir32}/${ldsoBasename32} - - - - -"
+      ] else [
+        "d /${libDir32} 0755 root root - -"
+        "L+ /${libDir32}/${ldsoBasename32} - - - - ${config.environment.ldso32}"
+      ]
+    );
+  };
+
+  meta.maintainers = with lib.maintainers; [ tejing ];
+}
diff --git a/nixos/modules/config/mysql.nix b/nixos/modules/config/mysql.nix
index 2f13c56f2ae59..4f72d22c4f0ec 100644
--- a/nixos/modules/config/mysql.nix
+++ b/nixos/modules/config/mysql.nix
@@ -6,6 +6,8 @@ let
   cfg = config.users.mysql;
 in
 {
+  meta.maintainers = [ maintainers.netali ];
+
   options = {
     users.mysql = {
       enable = mkEnableOption (lib.mdDoc "Authentication against a MySQL/MariaDB database");
@@ -358,7 +360,7 @@ in
       user = "root";
       group = "root";
       mode = "0600";
-      # password will be added from password file in activation script
+      # password will be added from password file in systemd oneshot
       text = ''
         users.host=${cfg.host}
         users.db_user=${cfg.user}
@@ -423,34 +425,45 @@ in
       mode = "0600";
       user = config.services.nscd.user;
       group = config.services.nscd.group;
-      # password will be added from password file in activation script
+      # password will be added from password file in systemd oneshot
       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
+    systemd.services.mysql-auth-pw-init = {
+      description = "Adds the mysql password to the mysql auth config files";
+
+      before = [ "nscd.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "root";
+        Group = "root";
+      };
 
-        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
+      restartTriggers = [
+        config.environment.etc."security/pam_mysql.conf".source
+        config.environment.etc."libnss-mysql.cfg".source
+        config.environment.etc."libnss-mysql-root.cfg".source
+      ];
 
-        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
+      script = ''
+        if [[ -r ${cfg.passwordFile} ]]; then
+          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
 
-        umask $org_umask
-      fi
-    '';
+          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
+        fi
+      '';
+    };
   };
 }
diff --git a/nixos/modules/config/nix-channel.nix b/nixos/modules/config/nix-channel.nix
index 3f8e088ede929..dd97cb730ae41 100644
--- a/nixos/modules/config/nix-channel.nix
+++ b/nixos/modules/config/nix-channel.nix
@@ -12,7 +12,6 @@ let
     mkDefault
     mkIf
     mkOption
-    stringAfter
     types
     ;
 
@@ -97,12 +96,8 @@ in
 
     nix.settings.nix-path = mkIf (! cfg.channel.enable) (mkDefault "");
 
-    system.activationScripts.nix-channel = mkIf cfg.channel.enable
-      (stringAfter [ "etc" "users" ] ''
-        # Subscribe the root user to the NixOS channel by default.
-        if [ ! -e "/root/.nix-channels" ]; then
-            echo "${config.system.defaultChannel} nixos" > "/root/.nix-channels"
-        fi
-      '');
+    systemd.tmpfiles.rules = lib.mkIf cfg.channel.enable [
+      ''f /root/.nix-channels - - - - ${config.system.defaultChannel} nixos\n''
+    ];
   };
 }
diff --git a/nixos/modules/config/nix.nix b/nixos/modules/config/nix.nix
index cee4f54db0cb5..2769d8b25ef6f 100644
--- a/nixos/modules/config/nix.nix
+++ b/nixos/modules/config/nix.nix
@@ -109,13 +109,17 @@ let
         if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then ''
           echo "Ignoring validation for cross-compilation"
         ''
-        else ''
+        else
+        let
+          showCommand = if isNixAtLeast "2.20pre" then "config show" else "show-config";
+        in
+        ''
           echo "Validating generated nix.conf"
           ln -s $out ./nix.conf
           set -e
           set +o pipefail
           NIX_CONF_DIR=$PWD \
-            ${cfg.package}/bin/nix show-config ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \
+            ${cfg.package}/bin/nix ${showCommand} ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \
               ${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \
             |& sed -e 's/^warning:/error:/' \
             | (! grep '${if cfg.checkAllErrors then "^error:" else "^error: unknown setting"}')
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index b2eb46f273b14..4727e5b85ef22 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -34,6 +34,8 @@ with lib;
       ffmpeg_5 = super.ffmpeg_5.override { ffmpegVariant = "headless"; };
       # dep of graphviz, libXpm is optional for Xpm support
       gd = super.gd.override { withXorg = false; };
+      ghostscript = super.ghostscript.override { cupsSupport = false; x11Support = false; };
+      gjs = super.gjs.overrideAttrs { doCheck = false; installTests = false; }; # avoid test dependency on gtk3
       gobject-introspection = super.gobject-introspection.override { x11Support = false; };
       gpsd = super.gpsd.override { guiSupport = false; };
       graphviz = super.graphviz-nox;
@@ -44,6 +46,7 @@ with lib;
       };
       imagemagick = super.imagemagick.override { libX11Support = false; libXtSupport = false; };
       imagemagickBig = super.imagemagickBig.override { libX11Support = false; libXtSupport = false; };
+      intel-vaapi-driver = super.intel-vaapi-driver.override { enableGui = false; };
       libdevil = super.libdevil-nox;
       libextractor = super.libextractor.override { gtkSupport = false; };
       libva = super.libva-minimal;
@@ -51,6 +54,7 @@ with lib;
       mc = super.mc.override { x11Support = false; };
       mpv-unwrapped = super.mpv-unwrapped.override { sdl2Support = false; x11Support = false; waylandSupport = false; };
       msmtp = super.msmtp.override { withKeyring = false; };
+      mupdf = super.mupdf.override { enableGL = false; enableX11 = false; };
       neofetch = super.neofetch.override { x11Support = false; };
       networkmanager-fortisslvpn = super.networkmanager-fortisslvpn.override { withGnome = false; };
       networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
@@ -71,7 +75,7 @@ with lib;
       qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
       qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; });
       qt5 = super.qt5.overrideScope (const (super': {
-        qtbase = super'.qtbase.override { withGtk3 = false; };
+        qtbase = super'.qtbase.override { withGtk3 = false; withQttranslation = false; };
       }));
       stoken = super.stoken.override { withGTK3 = false; };
       # translateManpages -> perlPackages.po4a -> texlive-combined-basic -> texlive-core-big -> libX11
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index 80ff6c1aabf74..662959bf0071b 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -8,8 +8,6 @@ let
   cfg = config.hardware.pulseaudio;
   alsaCfg = config.sound;
 
-  systemWide = cfg.enable && cfg.systemWide;
-  nonSystemWide = cfg.enable && !cfg.systemWide;
   hasZeroconf = let z = cfg.zeroconf; in z.publish.enable || z.discovery.enable;
 
   overriddenPackage = cfg.package.override
@@ -217,16 +215,10 @@ in {
   };
 
 
-  config = mkMerge [
+  config = lib.mkIf cfg.enable (mkMerge [
     {
-      environment.etc = {
-        "pulse/client.conf".source = clientConf;
-      };
-
-      hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa";
-    }
+      environment.etc."pulse/client.conf".source = clientConf;
 
-    (mkIf cfg.enable {
       environment.systemPackages = [ overriddenPackage ];
 
       sound.enable = true;
@@ -242,6 +234,8 @@ in {
         "libao.conf".source = writeText "libao.conf" "default_driver=pulse";
       };
 
+      hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa";
+
       # Disable flat volumes to enable relative ones
       hardware.pulseaudio.daemon.config.flat-volumes = mkDefault "no";
 
@@ -255,7 +249,7 @@ in {
 
       # PulseAudio is packaged with udev rules to handle various audio device quirks
       services.udev.packages = [ overriddenPackage ];
-    })
+    }
 
     (mkIf (cfg.extraModules != []) {
       hardware.pulseaudio.daemon.config.dl-search-path = let
@@ -277,7 +271,7 @@ in {
       services.avahi.publish.userServices = true;
     })
 
-    (mkIf nonSystemWide {
+    (mkIf (!cfg.systemWide) {
       environment.etc = {
         "pulse/default.pa".source = myConfigFile;
       };
@@ -297,7 +291,7 @@ in {
       };
     })
 
-    (mkIf systemWide {
+    (mkIf cfg.systemWide {
       users.users.pulse = {
         # For some reason, PulseAudio wants UID == GID.
         uid = assert uid == gid; uid;
@@ -305,6 +299,7 @@ in {
         extraGroups = [ "audio" ];
         description = "PulseAudio system service user";
         home = stateDir;
+        homeMode = "755";
         createHome = true;
         isSystemUser = true;
       };
@@ -327,6 +322,6 @@ in {
 
       environment.variables.PULSE_COOKIE = "${stateDir}/.config/pulse/cookie";
     })
-  ];
+  ]);
 
 }
diff --git a/nixos/modules/config/qt.nix b/nixos/modules/config/qt.nix
index 2b09281e467f9..f82b7ab85a8c3 100644
--- a/nixos/modules/config/qt.nix
+++ b/nixos/modules/config/qt.nix
@@ -1,121 +1,154 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   cfg = config.qt;
 
-  isQGnome = cfg.platformTheme == "gnome" && builtins.elem cfg.style ["adwaita" "adwaita-dark"];
-  isQtStyle = cfg.platformTheme == "gtk2" && !(builtins.elem cfg.style ["adwaita" "adwaita-dark"]);
-  isQt5ct = cfg.platformTheme == "qt5ct";
-  isLxqt = cfg.platformTheme == "lxqt";
-  isKde = cfg.platformTheme == "kde";
-
-  packages =
-    if isQGnome then [
-      pkgs.qgnomeplatform
-      pkgs.adwaita-qt
-      pkgs.qgnomeplatform-qt6
-      pkgs.adwaita-qt6
-    ]
-    else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins pkgs.qt6Packages.qt6gtk2 ]
-    else if isQt5ct then [ pkgs.libsForQt5.qt5ct pkgs.qt6Packages.qt6ct ]
-    else if isLxqt then [ pkgs.lxqt.lxqt-qtplugin pkgs.lxqt.lxqt-config ]
-    else if isKde then [ pkgs.libsForQt5.plasma-integration pkgs.libsForQt5.systemsettings ]
-    else throw "`qt.platformTheme` ${cfg.platformTheme} and `qt.style` ${cfg.style} are not compatible.";
+  platformPackages = with pkgs; {
+    gnome = [ qgnomeplatform qgnomeplatform-qt6 ];
+    gtk2 = [ libsForQt5.qtstyleplugins qt6Packages.qt6gtk2 ];
+    kde = [ libsForQt5.plasma-integration libsForQt5.systemsettings ];
+    lxqt = [ lxqt.lxqt-qtplugin lxqt.lxqt-config ];
+    qt5ct = [ libsForQt5.qt5ct qt6Packages.qt6ct ];
+  };
+
+  stylePackages = with pkgs; {
+    bb10bright = [ libsForQt5.qtstyleplugins ];
+    bb10dark = [ libsForQt5.qtstyleplugins ];
+    cde = [ libsForQt5.qtstyleplugins ];
+    cleanlooks = [ libsForQt5.qtstyleplugins ];
+    gtk2 = [ libsForQt5.qtstyleplugins qt6Packages.qt6gtk2 ];
+    motif = [ libsForQt5.qtstyleplugins ];
+    plastique = [ libsForQt5.qtstyleplugins ];
 
-in
+    adwaita = [ adwaita-qt adwaita-qt6 ];
+    adwaita-dark = [ adwaita-qt adwaita-qt6 ];
+    adwaita-highcontrast = [ adwaita-qt adwaita-qt6 ];
+    adwaita-highcontrastinverse = [ adwaita-qt adwaita-qt6 ];
+
+    breeze = [ libsForQt5.breeze-qt5 ];
 
+    kvantum = [ libsForQt5.qtstyleplugin-kvantum qt6Packages.qtstyleplugin-kvantum ];
+  };
+in
 {
-  meta.maintainers = [ maintainers.romildo ];
+  meta.maintainers = with lib.maintainers; [ romildo thiagokokada ];
 
   imports = [
-    (mkRenamedOptionModule ["qt5" "enable" ] ["qt" "enable" ])
-    (mkRenamedOptionModule ["qt5" "platformTheme" ] ["qt" "platformTheme" ])
-    (mkRenamedOptionModule ["qt5" "style" ] ["qt" "style" ])
+    (lib.mkRenamedOptionModule [ "qt5" "enable" ] [ "qt" "enable" ])
+    (lib.mkRenamedOptionModule [ "qt5" "platformTheme" ] [ "qt" "platformTheme" ])
+    (lib.mkRenamedOptionModule [ "qt5" "style" ] [ "qt" "style" ])
   ];
 
   options = {
     qt = {
+      enable = lib.mkEnableOption "" // {
+        description = lib.mdDoc ''
+          Whether to enable Qt configuration, including theming.
 
-      enable = mkEnableOption (lib.mdDoc "Qt theming configuration");
+          Enabling this option is necessary for Qt plugins to work in the
+          installed profiles (e.g.: `nix-env -i` or `environment.systemPackages`).
+        '';
+      };
 
-      platformTheme = mkOption {
-        type = types.enum [
-          "gtk2"
-          "gnome"
-          "lxqt"
-          "qt5ct"
-          "kde"
-        ];
+      platformTheme = lib.mkOption {
+        type = with lib.types; nullOr (enum (lib.attrNames platformPackages));
+        default = null;
         example = "gnome";
         relatedPackages = [
           "qgnomeplatform"
           "qgnomeplatform-qt6"
-          ["libsForQt5" "qtstyleplugins"]
-          ["libsForQt5" "qt5ct"]
-          ["lxqt" "lxqt-qtplugin"]
-          ["libsForQt5" "plasma-integration"]
+          [ "libsForQt5" "plasma-integration" ]
+          [ "libsForQt5" "qt5ct" ]
+          [ "libsForQt5" "qtstyleplugins" ]
+          [ "libsForQt5" "systemsettings" ]
+          [ "lxqt" "lxqt-config" ]
+          [ "lxqt" "lxqt-qtplugin" ]
+          [ "qt6Packages" "qt6ct" ]
+          [ "qt6Packages" "qt6gtk2" ]
         ];
         description = lib.mdDoc ''
           Selects the platform theme to use for Qt 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)
+          - `gtk2`: Use GTK theme with [qtstyleplugins](https://github.com/qt/qtstyleplugins)
+          - `kde`: Use Qt settings from Plasma.
           - `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.
+             and [qt6ct](https://github.com/trialuser02/qt6ct) applications.
         '';
       };
 
-      style = mkOption {
-        type = types.enum [
-          "adwaita"
-          "adwaita-dark"
-          "cleanlooks"
-          "gtk2"
-          "motif"
-          "plastique"
-        ];
+      style = lib.mkOption {
+        type = with lib.types; nullOr (enum (lib.attrNames stylePackages));
+        default = null;
         example = "adwaita";
         relatedPackages = [
           "adwaita-qt"
           "adwaita-qt6"
-          ["libsForQt5" "qtstyleplugins"]
-          ["qt6Packages" "qt6gtk2"]
+          [ "libsForQt5" "breeze-qt5" ]
+          [ "libsForQt5" "qtstyleplugin-kvantum" ]
+          [ "libsForQt5" "qtstyleplugins" ]
+          [ "qt6Packages" "qt6gtk2" ]
+          [ "qt6Packages" "qtstyleplugin-kvantum" ]
         ];
         description = lib.mdDoc ''
           Selects the style to use for Qt applications.
 
           The options are
-          - `adwaita`, `adwaita-dark`: Use Adwaita Qt style with
+          - `adwaita`, `adwaita-dark`, `adwaita-highcontrast`, `adawaita-highcontrastinverse`:
+            Use Adwaita Qt style with
             [adwaita](https://github.com/FedoraQt/adwaita-qt)
-          - `cleanlooks`, `gtk2`, `motif`, `plastique`: Use styles from
+          - `breeze`: Use the Breeze style from
+            [breeze](https://github.com/KDE/breeze)
+          - `bb10bright`, `bb10dark`, `cleanlooks`, `gtk2`, `motif`, `plastique`:
+            Use styles from
             [qtstyleplugins](https://github.com/qt/qtstyleplugins)
+          - `kvantum`: Use styles from
+            [kvantum](https://github.com/tsujan/Kvantum)
         '';
       };
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
+    assertions =
+      let
+        gnomeStyles = [
+          "adwaita"
+          "adwaita-dark"
+          "adwaita-highcontrast"
+          "adwaita-highcontrastinverse"
+          "breeze"
+        ];
+      in
+      [
+        {
+          assertion = cfg.platformTheme == "gnome" -> (builtins.elem cfg.style gnomeStyles);
+          message = ''
+            `qt.platformTheme` "gnome" must have `qt.style` set to a theme that supports both Qt and Gtk,
+            for example: ${lib.concatStringsSep ", " gnomeStyles}.
+          '';
+        }
+      ];
 
     environment.variables = {
-      QT_QPA_PLATFORMTHEME = cfg.platformTheme;
-      QT_STYLE_OVERRIDE = mkIf (! (isQt5ct || isLxqt || isKde)) cfg.style;
+      QT_QPA_PLATFORMTHEME = lib.mkIf (cfg.platformTheme != null) cfg.platformTheme;
+      QT_STYLE_OVERRIDE = lib.mkIf (cfg.style != null) cfg.style;
     };
 
-    environment.profileRelativeSessionVariables = let
-      qtVersions = with pkgs; [ qt5 qt6 ];
-    in {
-      QT_PLUGIN_PATH = map (qt: "/${qt.qtbase.qtPluginPrefix}") qtVersions;
-      QML2_IMPORT_PATH = map (qt: "/${qt.qtbase.qtQmlPrefix}") qtVersions;
-    };
-
-    environment.systemPackages = packages;
+    environment.profileRelativeSessionVariables =
+      let
+        qtVersions = with pkgs; [ qt5 qt6 ];
+      in
+      {
+        QT_PLUGIN_PATH = map (qt: "/${qt.qtbase.qtPluginPrefix}") qtVersions;
+        QML2_IMPORT_PATH = map (qt: "/${qt.qtbase.qtQmlPrefix}") qtVersions;
+      };
 
+    environment.systemPackages =
+      lib.optionals (cfg.platformTheme != null) (platformPackages.${cfg.platformTheme})
+      ++ lib.optionals (cfg.style != null) (stylePackages.${cfg.style});
   };
 }
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index bc6583442edf2..a8476bd2aaedd 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -214,7 +214,8 @@ in
       ''
         # Create the required /bin/sh symlink; otherwise lots of things
         # (notably the system() function) won't work.
-        mkdir -m 0755 -p /bin
+        mkdir -p /bin
+        chmod 0755 /bin
         ln -sfn "${cfg.binsh}" /bin/.sh.tmp
         mv /bin/.sh.tmp /bin/sh # atomically replace /bin/sh
       '';
diff --git a/nixos/modules/config/stevenblack.nix b/nixos/modules/config/stevenblack.nix
index 07a0aa339a561..7e6235169847a 100644
--- a/nixos/modules/config/stevenblack.nix
+++ b/nixos/modules/config/stevenblack.nix
@@ -15,7 +15,7 @@ let
 in
 {
   options.networking.stevenblack = {
-    enable = mkEnableOption (mdDoc "Enable the stevenblack hosts file blocklist");
+    enable = mkEnableOption (mdDoc "the stevenblack hosts file blocklist");
 
     block = mkOption {
       type = types.listOf (types.enum [ "fakenews" "gambling" "porn" "social" ]);
@@ -30,5 +30,5 @@ in
       ++ optionals (activatedHosts == [ ]) [ "${pkgs.stevenblack-blocklist}/hosts" ];
   };
 
-  meta.maintainers = [ maintainers.fortuneteller2k maintainers.artturin ];
+  meta.maintainers = [ maintainers.moni maintainers.artturin ];
 }
diff --git a/nixos/modules/config/stub-ld.nix b/nixos/modules/config/stub-ld.nix
new file mode 100644
index 0000000000000..14c07466d0611
--- /dev/null
+++ b/nixos/modules/config/stub-ld.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) optionalString mkOption types mdDoc mkIf mkDefault;
+
+  cfg = config.environment.stub-ld;
+
+  message = ''
+    NixOS cannot run dynamically linked executables intended for generic
+    linux environments out of the box. For more information, see:
+    https://nix.dev/permalink/stub-ld
+  '';
+
+  stub-ld-for = pkgsArg: messageArg: pkgsArg.pkgsStatic.runCommandCC "stub-ld" {
+    nativeBuildInputs = [ pkgsArg.unixtools.xxd ];
+    inherit messageArg;
+  } ''
+    printf "%s" "$messageArg" | xxd -i -n message >main.c
+    cat <<EOF >>main.c
+    #include <stdio.h>
+    int main(int argc, char * argv[]) {
+      fprintf(stderr, "Could not start dynamically linked executable: %s\n", argv[0]);
+      fwrite(message, sizeof(unsigned char), message_len, stderr);
+      return 127; // matches behavior of bash and zsh without a loader. fish uses 139
+    }
+    EOF
+    $CC -Os main.c -o $out
+  '';
+
+  pkgs32 = pkgs.pkgsi686Linux;
+
+  stub-ld = stub-ld-for pkgs message;
+  stub-ld32 = stub-ld-for pkgs32 message;
+in {
+  options = {
+    environment.stub-ld = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        example = false;
+        description = mdDoc ''
+          Install a stub ELF loader to print an informative error message
+          in the event that a user attempts to run an ELF binary not
+          compiled for NixOS.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.ldso = mkDefault stub-ld;
+    environment.ldso32 = mkIf pkgs.stdenv.isx86_64 (mkDefault stub-ld32);
+  };
+
+  meta.maintainers = with lib.maintainers; [ tejing ];
+}
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
index 8989a64082643..21046d6f1697b 100644
--- a/nixos/modules/config/swap.nix
+++ b/nixos/modules/config/swap.nix
@@ -258,7 +258,8 @@ in
             # avoid this race condition.
             after = [ "systemd-modules-load.service" ];
             wantedBy = [ "${realDevice'}.swap" ];
-            before = [ "${realDevice'}.swap" ];
+            before = [ "${realDevice'}.swap" "shutdown.target"];
+            conflicts = [ "shutdown.target" ];
             path = [ pkgs.util-linux pkgs.e2fsprogs ]
               ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
 
diff --git a/nixos/modules/config/sysctl.nix b/nixos/modules/config/sysctl.nix
index 0bc7ab9667f9a..bedba984a3c23 100644
--- a/nixos/modules/config/sysctl.nix
+++ b/nixos/modules/config/sysctl.nix
@@ -21,18 +21,28 @@ in
   options = {
 
     boot.kernel.sysctl = mkOption {
-      type = types.submodule {
+      type = let
+        highestValueType = types.ints.unsigned // {
+          merge = loc: defs:
+            foldl
+              (a: b: if b.value == null then null else lib.max a b.value)
+              0
+              (filterOverrides defs);
+        };
+      in 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);
+        options = {
+          "net.core.rmem_max" = mkOption {
+            type = types.nullOr highestValueType;
+            default = null;
+            description = lib.mdDoc "The maximum receive socket buffer size in bytes. In case of conflicting values, the highest will be used.";
+          };
+
+          "net.core.wmem_max" = mkOption {
+            type = types.nullOr highestValueType;
+            default = null;
+            description = lib.mdDoc "The maximum send socket buffer size in bytes. In case of conflicting values, the highest will be used.";
           };
-          default = null;
-          description = lib.mdDoc "The maximum socket receive buffer size. In case of conflicting values, the highest will be used.";
         };
       };
       default = {};
@@ -69,9 +79,6 @@ in
     # users as these make it easier to exploit kernel vulnerabilities.
     boot.kernel.sysctl."kernel.kptr_restrict" = mkDefault 1;
 
-    # Disable YAMA by default to allow easy debugging.
-    boot.kernel.sysctl."kernel.yama.ptrace_scope" = mkDefault 0;
-
     # Improve compatibility with applications that allocate
     # a lot of memory, like modern games
     boot.kernel.sysctl."vm.max_map_count" = mkDefault 1048576;
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index e8bbeac4f7206..71274ea8999fd 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -42,8 +42,7 @@ let
     ];
 
   defaultPackageNames =
-    [ "nano"
-      "perl"
+    [ "perl"
       "rsync"
       "strace"
     ];
@@ -90,12 +89,6 @@ in
           for a running system, entries can be removed for a more
           minimal NixOS installation.
 
-          Note: If `pkgs.nano` is removed from this list,
-          make sure another editor is installed and the
-          `EDITOR` environment variable is set to it.
-          Environment variables can be set using
-          {option}`environment.variables`.
-
           Like with systemPackages, packages are installed to
           {file}`/run/current-system/sw`. They are
           automatically available to all users, and are
@@ -116,8 +109,14 @@ in
       extraOutputsToInstall = mkOption {
         type = types.listOf types.str;
         default = [ ];
-        example = [ "doc" "info" "devdoc" ];
-        description = lib.mdDoc "List of additional package outputs to be symlinked into {file}`/run/current-system/sw`.";
+        example = [ "dev" "info" ];
+        description = lib.mdDoc ''
+          Entries listed here will be appended to the `meta.outputsToInstall` attribute for each package in `environment.systemPackages`, and the files from the corresponding derivation outputs symlinked into {file}`/run/current-system/sw`.
+
+          For example, this can be used to install the `dev` and `info` outputs for all packages in the system environment, if they are available.
+
+          To use specific outputs instead of configuring them globally, select the corresponding attribute on the package derivation, e.g. `libxml2.dev` or `coreutils.info`.
+        '';
       };
 
       extraSetup = mkOption {
diff --git a/nixos/modules/config/terminfo.nix b/nixos/modules/config/terminfo.nix
index 82f9ae48372af..ebd1aaea8f04a 100644
--- a/nixos/modules/config/terminfo.nix
+++ b/nixos/modules/config/terminfo.nix
@@ -6,26 +6,45 @@ with lib;
 
 {
 
-  options.environment.enableAllTerminfo = with lib; mkOption {
-    default = false;
-    type = types.bool;
-    description = lib.mdDoc ''
-      Whether to install all terminfo outputs
-    '';
+  options = with lib; {
+    environment.enableAllTerminfo = mkOption {
+      default = false;
+      type = types.bool;
+      description = lib.mdDoc ''
+        Whether to install all terminfo outputs
+      '';
+    };
+
+    security.sudo.keepTerminfo = mkOption {
+      default = true;
+      type = types.bool;
+      description = lib.mdDoc ''
+        Whether to preserve the `TERMINFO` and `TERMINFO_DIRS`
+        environment variables, for `root` and the `wheel` group.
+      '';
+    };
   };
 
   config = {
 
-    # can be generated with: filter (drv: (builtins.tryEval (drv ? terminfo)).value) (attrValues pkgs)
+    # can be generated with:
+    # attrNames (filterAttrs
+    #  (_: drv: (builtins.tryEval (isDerivation drv && drv ? terminfo)).value)
+    #  pkgs)
     environment.systemPackages = mkIf config.environment.enableAllTerminfo (map (x: x.terminfo) (with pkgs; [
       alacritty
+      contour
       foot
       kitty
       mtm
+      rio
       rxvt-unicode-unwrapped
       rxvt-unicode-unwrapped-emoji
+      st
       termite
+      tmux
       wezterm
+      yaft
     ]));
 
     environment.pathsToLink = [
@@ -46,7 +65,7 @@ with lib;
       export TERM=$TERM
     '';
 
-    security.sudo.extraConfig = ''
+    security.sudo.extraConfig = mkIf config.security.sudo.keepTerminfo ''
 
       # Keep terminfo database for root and %wheel.
       Defaults:root,%wheel env_keep+=TERMINFO_DIRS
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index 3785ba8c5fb80..7c6851473f42f 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -239,12 +239,12 @@ foreach my $u (@{$spec->{users}}) {
         chmod oct($u->{homeMode}), $u->{home};
     }
 
-    if (defined $u->{passwordFile}) {
-        if (-e $u->{passwordFile}) {
-            $u->{hashedPassword} = read_file($u->{passwordFile});
+    if (defined $u->{hashedPasswordFile}) {
+        if (-e $u->{hashedPasswordFile}) {
+            $u->{hashedPassword} = read_file($u->{hashedPasswordFile});
             chomp $u->{hashedPassword};
         } else {
-            warn "warning: password file ‘$u->{passwordFile}’ does not exist\n";
+            warn "warning: password file ‘$u->{hashedPasswordFile}’ does not exist\n";
         }
     } elsif (defined $u->{password}) {
         $u->{hashedPassword} = hashPassword($u->{password});
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 684b4bc8fbcc5..967ad0846d75b 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -18,11 +18,11 @@ let
 
   passwordDescription = ''
     The options {option}`hashedPassword`,
-    {option}`password` and {option}`passwordFile`
+    {option}`password` and {option}`hashedPasswordFile`
     controls what password is set for the user.
     {option}`hashedPassword` overrides both
-    {option}`password` and {option}`passwordFile`.
-    {option}`password` overrides {option}`passwordFile`.
+    {option}`password` and {option}`hashedPasswordFile`.
+    {option}`password` overrides {option}`hashedPasswordFile`.
     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` is true, the
@@ -153,7 +153,7 @@ let
           {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>
+          See <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>
           for more information.
         '';
       };
@@ -172,6 +172,17 @@ let
         '';
       };
 
+      ignoreShellProgramCheck = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          By default, nixos will check that programs.SHELL.enable is set to
+          true if the user has a custom shell specified. If that behavior isn't
+          required and there are custom overrides in place to make sure that the
+          shell is functional, set this to true.
+        '';
+      };
+
       subUidRanges = mkOption {
         type = with types; listOf (submodule subordinateUidRange);
         default = [];
@@ -250,18 +261,26 @@ let
         '';
       };
 
-      passwordFile = mkOption {
+      hashedPasswordFile = mkOption {
         type = with types; nullOr str;
-        default = null;
+        default = cfg.users.${name}.passwordFile;
+        defaultText = literalExpression "null";
         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 `chpasswd -e` command.
+          The full path to a file that contains the hash of 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 `chpasswd -e` command.
           ${passwordDescription}
         '';
       };
 
+      passwordFile = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        visible = false;
+        description = lib.mdDoc "Deprecated alias of hashedPasswordFile";
+      };
+
       initialHashedPassword = mkOption {
         type = with types; nullOr (passwdEntry str);
         default = null;
@@ -322,6 +341,20 @@ let
           administrator before being able to use the system again.
         '';
       };
+
+      linger = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to enable lingering for this user. If true, systemd user
+          units will start at boot, rather than starting at login and stopping
+          at logout. This is the declarative equivalent of running
+          `loginctl enable-linger` for this user.
+
+          If false, user units will not be started until the user logs in, and
+          may be stopped on logout depending on the settings in `logind.conf`.
+        '';
+      };
     };
 
     config = mkMerge
@@ -441,13 +474,15 @@ let
   gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
   sdInitrdUidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) config.boot.initrd.systemd.users) "uid";
   sdInitrdGidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) config.boot.initrd.systemd.groups) "gid";
+  groupNames = lib.mapAttrsToList (n: g: g.name) cfg.groups;
+  usersWithoutExistingGroup = lib.filterAttrs (n: u: u.group != "" && !lib.elem u.group groupNames) cfg.users;
 
   spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
     inherit (cfg) mutableUsers;
     users = mapAttrsToList (_: u:
       { inherit (u)
           name uid group description home homeMode createHome isSystemUser
-          password passwordFile hashedPassword
+          password hashedPasswordFile hashedPassword
           autoSubUidGidRange subUidRanges subGidRanges
           initialPassword initialHashedPassword expires;
         shell = utils.toShellPath u.shell;
@@ -571,6 +606,14 @@ in {
           defaultText = literalExpression "config.users.users.\${name}.group";
           default = cfg.users.${name}.group;
         };
+        options.shell = mkOption {
+          type = types.passwdEntry types.path;
+          description = ''
+            The path to the user's shell in initrd.
+          '';
+          default = "${pkgs.shadow}/bin/nologin";
+          defaultText = literalExpression "\${pkgs.shadow}/bin/nologin";
+        };
       }));
     };
 
@@ -642,7 +685,7 @@ in {
       shadow.gid = ids.gids.shadow;
     };
 
-    system.activationScripts.users = {
+    system.activationScripts.users = if !config.systemd.sysusers.enable then {
       supportsDryActivation = true;
       text = ''
         install -m 0700 -d /root
@@ -651,10 +694,26 @@ in {
         ${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
         -w ${./update-users-groups.pl} ${spec}
       '';
-    };
+    } else ""; # keep around for backwards compatibility
+
+    system.activationScripts.update-lingering = let
+      lingerDir = "/var/lib/systemd/linger";
+      lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger)));
+      lingeringUsersFile = builtins.toFile "lingering-users"
+        (concatStrings (map (s: "${s}\n")
+          (sort (a: b: a < b) lingeringUsers)));  # this sorting is important for `comm` to work correctly
+    in stringAfter [ "users" ] ''
+      if [ -e ${lingerDir} ] ; then
+        cd ${lingerDir}
+        ls ${lingerDir} | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger
+        ls ${lingerDir} | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl  enable-linger
+      fi
+    '';
 
     # Warn about user accounts with deprecated password hashing schemes
-    system.activationScripts.hashes = {
+    # This does not work when the users and groups are created by
+    # systemd-sysusers because the users are created too late then.
+    system.activationScripts.hashes = if !config.systemd.sysusers.enable then {
       deps = [ "users" ];
       text = ''
         users=()
@@ -672,7 +731,7 @@ in {
           printf ' - %s\n' "''${users[@]}"
         fi
       '';
-    };
+    } else ""; # keep around for backwards compatibility
 
     # for backwards compatibility
     system.activationScripts.groups = stringAfter [ "users" ] "";
@@ -692,6 +751,8 @@ in {
 
     environment.profiles = [
       "$HOME/.nix-profile"
+      "\${XDG_STATE_HOME}/nix/profile"
+      "$HOME/.local/state/nix/profile"
       "/etc/profiles/per-user/$USER"
     ];
 
@@ -699,17 +760,20 @@ in {
     boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
       contents = {
         "/etc/passwd".text = ''
-          ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group }: let
+          ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group, shell }: let
             g = config.boot.initrd.systemd.groups.${group};
-          in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:") config.boot.initrd.systemd.users)}
+          in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:${shell}") config.boot.initrd.systemd.users)}
         '';
         "/etc/group".text = ''
           ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
         '';
+        "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n";
       };
 
+      storePaths = [ "${pkgs.shadow}/bin/nologin" ];
+
       users = {
-        root = {};
+        root = { shell = lib.mkDefault "/bin/bash"; };
         nobody = {};
       };
 
@@ -741,6 +805,18 @@ in {
       { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
         message = "systemd initrd UIDs and GIDs must be unique!";
       }
+      { assertion = usersWithoutExistingGroup == {};
+        message =
+          let
+            errUsers = lib.attrNames usersWithoutExistingGroup;
+            missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup);
+            mkConfigHint = group: "users.groups.${group} = {};";
+          in ''
+            The following users have a primary group that is undefined: ${lib.concatStringsSep " " errUsers}
+            Hint: Add this to your NixOS configuration:
+              ${lib.concatStringsSep "\n  " (map mkConfigHint missingGroups)}
+          '';
+      }
       { # If mutableUsers is false, to prevent users creating a
         # configuration that locks them out of the system, ensure that
         # there is at least one "privileged" account that has a
@@ -756,7 +832,7 @@ in {
             &&
             (allowsLogin cfg.hashedPassword
              || cfg.password != null
-             || cfg.passwordFile != null
+             || cfg.hashedPasswordFile != null
              || cfg.openssh.authorizedKeys.keys != []
              || cfg.openssh.authorizedKeys.keyFiles != [])
           ) cfg.users ++ [
@@ -801,13 +877,17 @@ in {
             '';
           }
         ] ++ (map (shell: {
-            assertion = (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
+            assertion = !user.ignoreShellProgramCheck -> (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
             message = ''
               users.users.${user.name}.shell is set to ${shell}, but
               programs.${shell}.enable is not true. This will cause the ${shell}
               shell to lack the basic nix directories in its PATH and might make
               logging in as that user impossible. You can fix it with:
               programs.${shell}.enable = true;
+
+              If you know what you're doing and you are fine with the behavior,
+              set users.users.${user.name}.ignoreShellProgramCheck = true;
+              instead.
             '';
           }) [
           "fish"
@@ -845,9 +925,13 @@ in {
           The password hash of user "${user.name}" may be invalid. You must set a
           valid hash or the user will be locked out of their account. Please
           check the value of option `users.users."${user.name}".hashedPassword`.''
-        else null
-      ));
-
+        else null)
+        ++ flip mapAttrsToList cfg.users (name: user:
+          if user.passwordFile != null then
+            ''The option `users.users."${name}".passwordFile' has been renamed '' +
+            ''to `users.users."${name}".hashedPasswordFile'.''
+          else null)
+      );
   };
 
 }
diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
index e19e5cf28b3bc..07d4fa76c2e8b 100644
--- a/nixos/modules/config/xdg/portal.nix
+++ b/nixos/modules/config/xdg/portal.nix
@@ -8,6 +8,10 @@ let
     mkRenamedOptionModule
     teams
     types;
+
+  associationOptions = with types; attrsOf (
+    coercedTo (either (listOf str) str) (x: lib.concatStringsSep ";" (lib.toList x)) str
+  );
 in
 
 {
@@ -72,20 +76,76 @@ in
         See [#160923](https://github.com/NixOS/nixpkgs/issues/160923) for more info.
       '';
     };
+
+    config = mkOption {
+      type = types.attrsOf associationOptions;
+      default = { };
+      example = {
+        x-cinnamon = {
+          default = [ "xapp" "gtk" ];
+        };
+        pantheon = {
+          default = [ "pantheon" "gtk" ];
+          "org.freedesktop.impl.portal.Secret" = [ "gnome-keyring" ];
+        };
+        common = {
+          default = [ "gtk" ];
+        };
+      };
+      description = lib.mdDoc ''
+        Sets which portal backend should be used to provide the implementation
+        for the requested interface. For details check {manpage}`portals.conf(5)`.
+
+        Configs will be linked to `/etx/xdg/xdg-desktop-portal/` with the name `$desktop-portals.conf`
+        for `xdg.portal.config.$desktop` and `portals.conf` for `xdg.portal.config.common`
+        as an exception.
+      '';
+    };
+
+    configPackages = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      example = lib.literalExpression "[ pkgs.gnome.gnome-session ]";
+      description = lib.mdDoc ''
+        List of packages that provide XDG desktop portal configuration, usually in
+        the form of `share/xdg-desktop-portal/$desktop-portals.conf`.
+
+        Note that configs in `xdg.portal.config` will be preferred if set.
+      '';
+    };
   };
 
   config =
     let
       cfg = config.xdg.portal;
       packages = [ pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
+      configPackages = cfg.configPackages;
+
       joinedPortals = pkgs.buildEnv {
         name = "xdg-portals";
         paths = packages;
         pathsToLink = [ "/share/xdg-desktop-portal/portals" "/share/applications" ];
       };
 
+      joinedPortalConfigs = pkgs.buildEnv {
+        name = "xdg-portal-configs";
+        paths = configPackages;
+        pathsToLink = [ "/share/xdg-desktop-portal" ];
+      };
     in
     mkIf cfg.enable {
+      warnings = lib.optional (cfg.configPackages == [ ] && cfg.config == { }) ''
+        xdg-desktop-portal 1.17 reworked how portal implementations are loaded, you
+        should either set `xdg.portal.config` or `xdg.portal.configPackages`
+        to specify which portal backend to use for the requested interface.
+
+        https://github.com/flatpak/xdg-desktop-portal/blob/1.18.1/doc/portals.conf.rst.in
+
+        If you simply want to keep the behaviour in < 1.17, which uses the first
+        portal implementation found in lexicographical order, use the following:
+
+        xdg.portal.config.common.default = "*";
+      '';
 
       assertions = [
         {
@@ -108,7 +168,14 @@ in
           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";
+          NIXOS_XDG_DESKTOP_PORTAL_CONFIG_DIR = mkIf (cfg.configPackages != [ ]) "${joinedPortalConfigs}/share/xdg-desktop-portal";
         };
+
+        etc = lib.concatMapAttrs
+          (desktop: conf: lib.optionalAttrs (conf != { }) {
+            "xdg/xdg-desktop-portal/${lib.optionalString (desktop != "common") "${desktop}-"}portals.conf".text =
+              lib.generators.toINI { } { preferred = conf; };
+          }) cfg.config;
       };
     };
 }
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index 9e7a01c58afe2..a97c8c418c865 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -18,29 +18,16 @@ in {
 
   options = {
 
-    hardware.enableAllFirmware = mkOption {
-      default = false;
-      type = types.bool;
-      description = lib.mdDoc ''
-        Turn on this option if you want to enable all the firmware.
-      '';
-    };
+    hardware.enableAllFirmware = mkEnableOption "all firmware regardless of license";
 
-    hardware.enableRedistributableFirmware = mkOption {
+    hardware.enableRedistributableFirmware = mkEnableOption "firmware with a license allowing redistribution" // {
       default = config.hardware.enableAllFirmware;
       defaultText = lib.literalExpression "config.hardware.enableAllFirmware";
-      type = types.bool;
-      description = lib.mdDoc ''
-        Turn on this option if you want to enable all the firmware with a license allowing redistribution.
-      '';
     };
 
-    hardware.wirelessRegulatoryDatabase = mkOption {
-      default = false;
-      type = types.bool;
-      description = lib.mdDoc ''
-        Load the wireless regulatory database at boot.
-      '';
+    hardware.wirelessRegulatoryDatabase = mkEnableOption "loading the wireless regulatory database at boot" // {
+      default = cfg.enableRedistributableFirmware || cfg.enableAllFirmware;
+      defaultText = literalMD "Enabled if proprietary firmware is allowed via {option}`enableRedistributableFirmware` or {option}`enableAllFirmware`.";
     };
 
   };
@@ -61,15 +48,11 @@ in {
         alsa-firmware
         sof-firmware
         libreelec-dvb-firmware
-      ] ++ optional pkgs.stdenv.hostPlatform.isAarch raspberrypiWirelessFirmware
-        ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
-        rtl8723bs-firmware
-      ];
-      hardware.wirelessRegulatoryDatabase = true;
+      ] ++ optional pkgs.stdenv.hostPlatform.isAarch raspberrypiWirelessFirmware;
     })
     (mkIf cfg.enableAllFirmware {
       assertions = [{
-        assertion = !cfg.enableAllFirmware || config.nixpkgs.config.allowUnfree;
+        assertion = !cfg.enableAllFirmware || pkgs.config.allowUnfree;
         message = ''
           the list of hardware.enableAllFirmware contains non-redistributable licensed firmware files.
             This requires nixpkgs.config.allowUnfree to be true.
diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix
index 79977939eec81..34f951a7446f4 100644
--- a/nixos/modules/hardware/ckb-next.nix
+++ b/nixos/modules/hardware/ckb-next.nix
@@ -24,14 +24,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.ckb-next;
-        defaultText = literalExpression "pkgs.ckb-next";
-        description = lib.mdDoc ''
-          The package implementing the Corsair keyboard/mouse driver.
-        '';
-      };
+      package = mkPackageOption pkgs "ckb-next" { };
     };
 
     config = mkIf cfg.enable {
diff --git a/nixos/modules/hardware/corectrl.nix b/nixos/modules/hardware/corectrl.nix
index 965cbe0267e08..8ef61a158d5ce 100644
--- a/nixos/modules/hardware/corectrl.nix
+++ b/nixos/modules/hardware/corectrl.nix
@@ -8,13 +8,13 @@ in
 {
   options.programs.corectrl = {
     enable = mkEnableOption (lib.mdDoc ''
-      A tool to overclock amd graphics cards and processors.
+      CoreCtrl, 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 (lib.mdDoc ''
-        true
+        GPU overclocking
       '');
       ppfeaturemask = mkOption {
         type = types.str;
diff --git a/nixos/modules/hardware/cpu/amd-sev.nix b/nixos/modules/hardware/cpu/amd-sev.nix
index 28ee07f005bab..08e1de496383b 100644
--- a/nixos/modules/hardware/cpu/amd-sev.nix
+++ b/nixos/modules/hardware/cpu/amd-sev.nix
@@ -1,37 +1,43 @@
-{ config, lib, ... }:
+{ config, options, 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";
-      };
+  cfgSev = config.hardware.cpu.amd.sev;
+  cfgSevGuest = config.hardware.cpu.amd.sevGuest;
+
+  optionsFor = device: group: {
+    enable = mkEnableOption (lib.mdDoc "access to the AMD ${device} device");
+    user = mkOption {
+      description = lib.mdDoc "Owner to assign to the ${device} device.";
+      type = types.str;
+      default = "root";
+    };
+    group = mkOption {
+      description = lib.mdDoc "Group to assign to the ${device} device.";
+      type = types.str;
+      default = group;
     };
+    mode = mkOption {
+      description = lib.mdDoc "Mode to set for the ${device} device.";
+      type = types.str;
+      default = "0660";
+    };
+  };
+in
+with lib; {
+  options.hardware.cpu.amd.sev = optionsFor "SEV" "sev";
+
+  options.hardware.cpu.amd.sevGuest = optionsFor "SEV guest" "sev-guest";
 
-    config = mkIf cfg.enable {
+  config = mkMerge [
+    # /dev/sev
+    (mkIf cfgSev.enable {
       assertions = [
         {
-          assertion = hasAttr cfg.user config.users.users;
+          assertion = hasAttr cfgSev.user config.users.users;
           message = "Given user does not exist";
         }
         {
-          assertion = (cfg.group == defaultGroup) || (hasAttr cfg.group config.users.groups);
+          assertion = (cfgSev.group == options.hardware.cpu.amd.sev.group.default) || (hasAttr cfgSev.group config.users.groups);
           message = "Given group does not exist";
         }
       ];
@@ -40,12 +46,35 @@ in
         options kvm_amd sev=1
       '';
 
-      users.groups = optionalAttrs (cfg.group == defaultGroup) {
-        "${cfg.group}" = {};
+      users.groups = optionalAttrs (cfgSev.group == options.hardware.cpu.amd.sev.group.default) {
+        "${cfgSev.group}" = { };
       };
 
-      services.udev.extraRules = with cfg; ''
+      services.udev.extraRules = with cfgSev; ''
         KERNEL=="sev", OWNER="${user}", GROUP="${group}", MODE="${mode}"
       '';
-    };
-  }
+    })
+
+    # /dev/sev-guest
+    (mkIf cfgSevGuest.enable {
+      assertions = [
+        {
+          assertion = hasAttr cfgSevGuest.user config.users.users;
+          message = "Given user does not exist";
+        }
+        {
+          assertion = (cfgSevGuest.group == options.hardware.cpu.amd.sevGuest.group.default) || (hasAttr cfgSevGuest.group config.users.groups);
+          message = "Given group does not exist";
+        }
+      ];
+
+      users.groups = optionalAttrs (cfgSevGuest.group == options.hardware.cpu.amd.sevGuest.group.default) {
+        "${cfgSevGuest.group}" = { };
+      };
+
+      services.udev.extraRules = with cfgSevGuest; ''
+        KERNEL=="sev-guest", OWNER="${user}", GROUP="${group}", MODE="${mode}"
+      '';
+    })
+  ];
+}
diff --git a/nixos/modules/hardware/cpu/x86-msr.nix b/nixos/modules/hardware/cpu/x86-msr.nix
new file mode 100644
index 0000000000000..554bec1b7db15
--- /dev/null
+++ b/nixos/modules/hardware/cpu/x86-msr.nix
@@ -0,0 +1,91 @@
+{ lib
+, config
+, options
+, ...
+}:
+let
+  inherit (builtins) hasAttr;
+  inherit (lib) mkIf mdDoc;
+  cfg = config.hardware.cpu.x86.msr;
+  opt = options.hardware.cpu.x86.msr;
+  defaultGroup = "msr";
+  isDefaultGroup = cfg.group == defaultGroup;
+  set = "to set for devices of the `msr` kernel subsystem.";
+
+  # Generates `foo=bar` parameters to pass to the kernel.
+  # If `module = baz` is passed, generates `baz.foo=bar`.
+  # Adds double quotes on demand to handle `foo="bar baz"`.
+  kernelParam = { module ? null }: name: value:
+    assert lib.asserts.assertMsg (!lib.strings.hasInfix "=" name) "kernel parameter cannot have '=' in name";
+    let
+      key = (if module == null then "" else module + ".") + name;
+      valueString = lib.generators.mkValueStringDefault {} value;
+      quotedValueString = if lib.strings.hasInfix " " valueString
+        then lib.strings.escape ["\""] valueString
+        else valueString;
+    in "${key}=${quotedValueString}";
+  msrKernelParam = kernelParam { module = "msr"; };
+in
+{
+  options.hardware.cpu.x86.msr = with lib.options; with lib.types; {
+    enable = mkEnableOption (mdDoc "the `msr` (Model-Specific Registers) kernel module and configure `udev` rules for its devices (usually `/dev/cpu/*/msr`)");
+    owner = mkOption {
+      type = str;
+      default = "root";
+      example = "nobody";
+      description = mdDoc "Owner ${set}";
+    };
+    group = mkOption {
+      type = str;
+      default = defaultGroup;
+      example = "nobody";
+      description = mdDoc "Group ${set}";
+    };
+    mode = mkOption {
+      type = str;
+      default = "0640";
+      example = "0660";
+      description = mdDoc "Mode ${set}";
+    };
+    settings = mkOption {
+      type = submodule {
+        freeformType = attrsOf (oneOf [ bool int str ]);
+        options.allow-writes = mkOption {
+          type = nullOr (enum ["on" "off"]);
+          default = null;
+          description = "Whether to allow writes to MSRs (`\"on\"`) or not (`\"off\"`).";
+        };
+      };
+      default = {};
+      description = "Parameters for the `msr` kernel module.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = hasAttr cfg.owner config.users.users;
+        message = "Owner '${cfg.owner}' set in `${opt.owner}` is not configured via `${options.users.users}.\"${cfg.owner}\"`.";
+      }
+      {
+        assertion = isDefaultGroup || (hasAttr cfg.group config.users.groups);
+        message = "Group '${cfg.group}' set in `${opt.group}` is not configured via `${options.users.groups}.\"${cfg.group}\"`.";
+      }
+    ];
+
+    boot = {
+      kernelModules = [ "msr" ];
+      kernelParams = lib.attrsets.mapAttrsToList msrKernelParam (lib.attrsets.filterAttrs (_: value: value != null) cfg.settings);
+    };
+
+    users.groups.${cfg.group} = mkIf isDefaultGroup { };
+
+    services.udev.extraRules = ''
+      SUBSYSTEM=="msr", OWNER="${cfg.owner}", GROUP="${cfg.group}", MODE="${cfg.mode}"
+    '';
+  };
+
+  meta = with lib; {
+    maintainers = with maintainers; [ lorenzleutgeb ];
+  };
+}
diff --git a/nixos/modules/hardware/decklink.nix b/nixos/modules/hardware/decklink.nix
new file mode 100644
index 0000000000000..d179e1d7634ff
--- /dev/null
+++ b/nixos/modules/hardware/decklink.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.hardware.decklink;
+  kernelPackages = config.boot.kernelPackages;
+in
+{
+  options.hardware.decklink.enable = lib.mkEnableOption "hardware support for the Blackmagic Design Decklink audio/video interfaces";
+
+  config = lib.mkIf cfg.enable {
+    boot.kernelModules = [ "blackmagic" "blackmagic-io" "snd_blackmagic-io" ];
+    boot.extraModulePackages = [ kernelPackages.decklink ];
+    systemd.packages = [ pkgs.blackmagic-desktop-video ];
+    systemd.services.DesktopVideoHelper.wantedBy = [ "multi-user.target" ];
+  };
+}
diff --git a/nixos/modules/hardware/device-tree.nix b/nixos/modules/hardware/device-tree.nix
index c568f52ab677d..6ab13c0eb7097 100644
--- a/nixos/modules/hardware/device-tree.nix
+++ b/nixos/modules/hardware/device-tree.nix
@@ -66,36 +66,32 @@ let
   };
 
   filterDTBs = src: if cfg.filter == null
-    then "${src}/dtbs"
+    then src
     else
       pkgs.runCommand "dtbs-filtered" {} ''
         mkdir -p $out
-        cd ${src}/dtbs
+        cd ${src}
         find . -type f -name '${cfg.filter}' -print0 \
           | xargs -0 cp -v --no-preserve=mode --target-directory $out --parents
       '';
 
-  filteredDTBs = filterDTBs cfg.kernelPackage;
-
-  # Compile single Device Tree overlay source
-  # file (.dts) into its compiled variant (.dtbo)
-  compileDTS = name: f: pkgs.callPackage({ stdenv, dtc }: stdenv.mkDerivation {
-    name = "${name}-dtbo";
-
-    nativeBuildInputs = [ dtc ];
-
-    buildCommand = ''
-      $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
-    '';
-  }) {};
+  filteredDTBs = filterDTBs cfg.dtbSource;
 
   # Fill in `dtboFile` for each overlay if not set already.
   # Existence of one of these is guarded by assertion below
   withDTBOs = xs: flip map xs (o: o // { dtboFile =
+    let
+      includePaths = ["${getDev cfg.kernelPackage}/lib/modules/${cfg.kernelPackage.modDirVersion}/source/scripts/dtc/include-prefixes"] ++ cfg.dtboBuildExtraIncludePaths;
+      extraPreprocessorFlags = cfg.dtboBuildExtraPreprocessorFlags;
+    in
     if o.dtboFile == null then
-      if o.dtsFile != null then compileDTS o.name o.dtsFile
-      else compileDTS o.name (pkgs.writeText "dts" o.dtsText)
+      let
+        dtsFile = if o.dtsFile == null then (pkgs.writeText "dts" o.dtsText) else o.dtsFile;
+      in
+      pkgs.deviceTree.compileDTS {
+        name = "${o.name}-dtbo";
+        inherit includePaths extraPreprocessorFlags dtsFile;
+      }
     else o.dtboFile; } );
 
 in
@@ -121,7 +117,39 @@ in
           example = literalExpression "pkgs.linux_latest";
           type = types.path;
           description = lib.mdDoc ''
-            Kernel package containing the base device-tree (.dtb) to boot. Uses
+            Kernel package where device tree include directory is from. Also used as default source of dtb package to apply overlays to
+          '';
+        };
+
+        dtboBuildExtraPreprocessorFlags = mkOption {
+          default = [];
+          example = literalExpression "[ \"-DMY_DTB_DEFINE\" ]";
+          type = types.listOf types.str;
+          description = lib.mdDoc ''
+            Additional flags to pass to the preprocessor during dtbo compilations
+          '';
+        };
+
+        dtboBuildExtraIncludePaths = mkOption {
+          default = [];
+          example = literalExpression ''
+            [
+              ./my_custom_include_dir_1
+              ./custom_include_dir_2
+            ]
+          '';
+          type = types.listOf types.path;
+          description = lib.mdDoc ''
+            Additional include paths that will be passed to the preprocessor when creating the final .dts to compile into .dtbo
+          '';
+        };
+
+        dtbSource = mkOption {
+          default = "${cfg.kernelPackage}/dtbs";
+          defaultText = literalExpression "\${cfg.kernelPackage}/dtbs";
+          type = types.path;
+          description = lib.mdDoc ''
+            Path to dtb directory that overlays and other processing will be applied to. Uses
             device trees bundled with the Linux kernel by default.
           '';
         };
diff --git a/nixos/modules/hardware/digitalbitbox.nix b/nixos/modules/hardware/digitalbitbox.nix
index 74e46bd34acea..ea04d72a63a55 100644
--- a/nixos/modules/hardware/digitalbitbox.nix
+++ b/nixos/modules/hardware/digitalbitbox.nix
@@ -16,11 +16,10 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.digitalbitbox;
-      defaultText = literalExpression "pkgs.digitalbitbox";
-      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.";
+    package = mkPackageOption pkgs "digitalbitbox" {
+      extraDescription = ''
+        This can be used to install a package with udev rules that differ from the defaults.
+      '';
     };
   };
 
diff --git a/nixos/modules/hardware/glasgow.nix b/nixos/modules/hardware/glasgow.nix
new file mode 100644
index 0000000000000..f8ebb772c47b1
--- /dev/null
+++ b/nixos/modules/hardware/glasgow.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.hardware.glasgow;
+
+in
+{
+  options.hardware.glasgow = {
+    enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enables Glasgow udev rules and ensures 'plugdev' group exists.
+        This is a prerequisite to using Glasgow without being root.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.udev.packages = [ pkgs.glasgow ];
+    users.groups.plugdev = { };
+  };
+}
diff --git a/nixos/modules/hardware/i2c.nix b/nixos/modules/hardware/i2c.nix
index 9a5a2e44813ed..bd4c4ebe21bde 100644
--- a/nixos/modules/hardware/i2c.nix
+++ b/nixos/modules/hardware/i2c.nix
@@ -11,7 +11,7 @@ in
     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.
+      logged on the computer locally
     '');
 
     group = mkOption {
diff --git a/nixos/modules/hardware/infiniband.nix b/nixos/modules/hardware/infiniband.nix
new file mode 100644
index 0000000000000..962883fa79721
--- /dev/null
+++ b/nixos/modules/hardware/infiniband.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.hardware.infiniband;
+  opensm-services = {
+    "opensm@" = {
+      enable = true;
+      description = "Starts OpenSM Infiniband fabric Subnet Managers";
+      before = [ "network.target"];
+      unitConfig = {
+        ConditionPathExists = "/sys/class/infiniband_mad/abi_version";
+      };
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.opensm}/bin/opensm --guid %I --log_file /var/log/opensm.%I.log";
+      };
+    };
+  } // (builtins.listToAttrs (map (guid: {
+    name = "opensm@${guid}";
+    value = {
+      enable = true;
+      wantedBy = [ "machines.target" ];
+      overrideStrategy = "asDropin";
+    };
+  } ) cfg.guids));
+
+in
+
+{
+  options.hardware.infiniband = {
+    enable = mkEnableOption "Infiniband support";
+    guids = mkOption {
+      type = with types; listOf str;
+      default = [];
+      example = [ "0xe8ebd30000eee2e1" ];
+      description = lib.mdDoc ''
+        A list of infiniband port guids on the system. This is discoverable using `ibstat -p`
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.initrd.kernelModules = [
+      "mlx5_core" "mlx5_ib" "ib_cm"
+      "rdma_cm" "rdma_ucm" "rpcrdma"
+      "ib_ipoib" "ib_isert" "ib_umad" "ib_uverbs"
+    ];
+    # rdma-core exposes ibstat, mstflint exposes mstconfig (which can be needed for
+    # setting link configurations), qperf needed to affirm link speeds
+    environment.systemPackages = with pkgs; [
+      rdma-core mstflint qperf
+    ];
+    systemd.services = opensm-services;
+  };
+}
diff --git a/nixos/modules/hardware/keyboard/qmk.nix b/nixos/modules/hardware/keyboard/qmk.nix
index df3bcaeccd2ec..d95d36dedb44e 100644
--- a/nixos/modules/hardware/keyboard/qmk.nix
+++ b/nixos/modules/hardware/keyboard/qmk.nix
@@ -12,5 +12,6 @@ in
 
   config = mkIf cfg.enable {
     services.udev.packages = [ pkgs.qmk-udev-rules ];
+    users.groups.plugdev = {};
   };
 }
diff --git a/nixos/modules/hardware/keyboard/uhk.nix b/nixos/modules/hardware/keyboard/uhk.nix
index 17baff83d886b..ff984fa5daa6b 100644
--- a/nixos/modules/hardware/keyboard/uhk.nix
+++ b/nixos/modules/hardware/keyboard/uhk.nix
@@ -11,7 +11,7 @@ in
       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.
+      You may want to install the uhk-agent package
     '');
 
   };
diff --git a/nixos/modules/hardware/keyboard/zsa.nix b/nixos/modules/hardware/keyboard/zsa.nix
index a04b67b5c8d0e..191fb12cca4f9 100644
--- a/nixos/modules/hardware/keyboard/zsa.nix
+++ b/nixos/modules/hardware/keyboard/zsa.nix
@@ -11,7 +11,7 @@ in
       udev rules for keyboards from ZSA like the ErgoDox EZ, Planck EZ and Moonlander Mark I.
       You need it when you want to flash a new configuration on the keyboard
       or use their live training in the browser.
-      You may want to install the wally-cli package.
+      You may want to install the wally-cli package
     '');
   };
 
diff --git a/nixos/modules/hardware/openrazer.nix b/nixos/modules/hardware/openrazer.nix
index aaa4000e758ff..abbafaee89501 100644
--- a/nixos/modules/hardware/openrazer.nix
+++ b/nixos/modules/hardware/openrazer.nix
@@ -50,7 +50,7 @@ in
   options = {
     hardware.openrazer = {
       enable = mkEnableOption (lib.mdDoc ''
-        OpenRazer drivers and userspace daemon.
+        OpenRazer drivers and userspace daemon
       '');
 
       verboseLogging = mkOption {
diff --git a/nixos/modules/hardware/opentabletdriver.nix b/nixos/modules/hardware/opentabletdriver.nix
index e3f418abce4f8..f103da14c9dd1 100644
--- a/nixos/modules/hardware/opentabletdriver.nix
+++ b/nixos/modules/hardware/opentabletdriver.nix
@@ -26,14 +26,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.opentabletdriver;
-        defaultText = literalExpression "pkgs.opentabletdriver";
-        description = lib.mdDoc ''
-          OpenTabletDriver derivation to use.
-        '';
-      };
+      package = mkPackageOption pkgs "opentabletdriver" { };
 
       daemon = {
         enable = mkOption {
diff --git a/nixos/modules/hardware/tuxedo-keyboard.nix b/nixos/modules/hardware/tuxedo-keyboard.nix
index 3ae876bd1f18b..fd8b48a5e9eaf 100644
--- a/nixos/modules/hardware/tuxedo-keyboard.nix
+++ b/nixos/modules/hardware/tuxedo-keyboard.nix
@@ -9,7 +9,7 @@ in
   {
     options.hardware.tuxedo-keyboard = {
       enable = mkEnableOption (lib.mdDoc ''
-          Enables the tuxedo-keyboard driver.
+          the tuxedo-keyboard driver.
 
           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.
diff --git a/nixos/modules/hardware/usb-storage.nix b/nixos/modules/hardware/usb-storage.nix
index 9c1b7a125fd18..3cb2c60d7ccd5 100644
--- a/nixos/modules/hardware/usb-storage.nix
+++ b/nixos/modules/hardware/usb-storage.nix
@@ -14,7 +14,7 @@ with lib;
 
   config = mkIf config.hardware.usbStorage.manageStartStop {
     services.udev.extraRules = ''
-      ACTION=="add|change", SUBSYSTEM=="scsi_disk", DRIVERS=="usb-storage", ATTR{manage_start_stop}="1"
+      ACTION=="add|change", SUBSYSTEM=="scsi_disk", DRIVERS=="usb-storage", ATTR{manage_system_start_stop}="1"
     '';
   };
 }
diff --git a/nixos/modules/hardware/video/amdgpu-pro.nix b/nixos/modules/hardware/video/amdgpu-pro.nix
index 299a30b0629b1..2a86280eec8cb 100644
--- a/nixos/modules/hardware/video/amdgpu-pro.nix
+++ b/nixos/modules/hardware/video/amdgpu-pro.nix
@@ -20,9 +20,6 @@ in
 {
 
   config = mkIf enabled {
-
-    nixpkgs.config.xorg.abiCompat = "1.20";
-
     services.xserver.drivers = singleton
       { name = "amdgpu"; modules = [ package ]; display = true; };
 
@@ -42,9 +39,10 @@ in
 
     hardware.firmware = [ package.fw ];
 
-    system.activationScripts.setup-amdgpu-pro = ''
-      ln -sfn ${package}/opt/amdgpu{,-pro} /run
-    '';
+    systemd.tmpfiles.settings.amdgpu-pro = {
+      "/run/amdgpu"."L+".argument = "${package}/opt/amdgpu";
+      "/run/amdgpu-pro"."L+".argument = "${package}/opt/amdgpu-pro";
+    };
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isYes "DEVICE_PRIVATE")
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 67c3afcf320a9..3b983f768f91a 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -4,8 +4,9 @@
   pkgs,
   ...
 }: let
+  nvidiaEnabled = (lib.elem "nvidia" config.services.xserver.videoDrivers);
   nvidia_x11 =
-    if (lib.elem "nvidia" config.services.xserver.videoDrivers)
+    if nvidiaEnabled || cfg.datacenter.enable
     then cfg.package
     else null;
 
@@ -18,23 +19,80 @@
   primeEnabled = syncCfg.enable || reverseSyncCfg.enable || offloadCfg.enable;
   busIDType = lib.types.strMatching "([[:print:]]+[\:\@][0-9]{1,3}\:[0-9]{1,2}\:[0-9])?";
   ibtSupport = cfg.open || (nvidia_x11.ibtSupport or false);
+  settingsFormat = pkgs.formats.keyValue {};
 in {
   options = {
     hardware.nvidia = {
+      datacenter.enable = lib.mkEnableOption (lib.mdDoc ''
+        Data Center drivers for NVIDIA cards on a NVLink topology
+      '');
+      datacenter.settings = lib.mkOption {
+        type = settingsFormat.type;
+        default = {
+          LOG_LEVEL=4;
+          LOG_FILE_NAME="/var/log/fabricmanager.log";
+          LOG_APPEND_TO_LOG=1;
+          LOG_FILE_MAX_SIZE=1024;
+          LOG_USE_SYSLOG=0;
+          DAEMONIZE=1;
+          BIND_INTERFACE_IP="127.0.0.1";
+          STARTING_TCP_PORT=16000;
+          FABRIC_MODE=0;
+          FABRIC_MODE_RESTART=0;
+          STATE_FILE_NAME="/var/tmp/fabricmanager.state";
+          FM_CMD_BIND_INTERFACE="127.0.0.1";
+          FM_CMD_PORT_NUMBER=6666;
+          FM_STAY_RESIDENT_ON_FAILURES=0;
+          ACCESS_LINK_FAILURE_MODE=0;
+          TRUNK_LINK_FAILURE_MODE=0;
+          NVSWITCH_FAILURE_MODE=0;
+          ABORT_CUDA_JOBS_ON_FM_EXIT=1;
+          TOPOLOGY_FILE_PATH="${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
+          DATABASE_PATH="${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
+        };
+        defaultText = lib.literalExpression ''
+        {
+          LOG_LEVEL=4;
+          LOG_FILE_NAME="/var/log/fabricmanager.log";
+          LOG_APPEND_TO_LOG=1;
+          LOG_FILE_MAX_SIZE=1024;
+          LOG_USE_SYSLOG=0;
+          DAEMONIZE=1;
+          BIND_INTERFACE_IP="127.0.0.1";
+          STARTING_TCP_PORT=16000;
+          FABRIC_MODE=0;
+          FABRIC_MODE_RESTART=0;
+          STATE_FILE_NAME="/var/tmp/fabricmanager.state";
+          FM_CMD_BIND_INTERFACE="127.0.0.1";
+          FM_CMD_PORT_NUMBER=6666;
+          FM_STAY_RESIDENT_ON_FAILURES=0;
+          ACCESS_LINK_FAILURE_MODE=0;
+          TRUNK_LINK_FAILURE_MODE=0;
+          NVSWITCH_FAILURE_MODE=0;
+          ABORT_CUDA_JOBS_ON_FM_EXIT=1;
+          TOPOLOGY_FILE_PATH="''${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
+          DATABASE_PATH="''${nvidia_x11.fabricmanager}/share/nvidia-fabricmanager/nvidia/nvswitch";
+        }
+        '';
+        description = lib.mdDoc ''
+          Additional configuration options for fabricmanager.
+        '';
+      };
+
       powerManagement.enable = lib.mkEnableOption (lib.mdDoc ''
         experimental power management through systemd. For more information, see
-        the NVIDIA docs, on Chapter 21. Configuring Power Management Support.
+        the NVIDIA docs, on Chapter 21. Configuring Power Management Support
       '');
 
       powerManagement.finegrained = lib.mkEnableOption (lib.mdDoc ''
         experimental power management of PRIME offload. For more information, see
-        the NVIDIA docs, on Chapter 22. PCI-Express Runtime D3 (RTD3) Power Management.
+        the NVIDIA docs, on Chapter 22. PCI-Express Runtime D3 (RTD3) Power Management
       '');
 
       dynamicBoost.enable = lib.mkEnableOption (lib.mdDoc ''
         dynamic Boost balances power between the CPU and the GPU for improved
         performance on supported laptops using the nvidia-powerd daemon. For more
-        information, see the NVIDIA docs, on Chapter 23. Dynamic Boost on Linux.
+        information, see the NVIDIA docs, on Chapter 23. Dynamic Boost on Linux
       '');
 
       modesetting.enable = lib.mkEnableOption (lib.mdDoc ''
@@ -43,7 +101,7 @@ in {
         Enabling this fixes screen tearing when using Optimus via PRIME (see
         {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.
+        work with SLI
       '');
 
       prime.nvidiaBusId = lib.mkOption {
@@ -97,11 +155,11 @@ in {
 
         Note that this configuration will only be successful when a display manager
         for which the {option}`services.xserver.displayManager.setupCommands`
-        option is supported is used.
+        option is supported is used
       '');
 
       prime.allowExternalGpu = lib.mkEnableOption (lib.mdDoc ''
-        configuring X to allow external NVIDIA GPUs when using Prime [Reverse] sync optimus.
+        configuring X to allow external NVIDIA GPUs when using Prime [Reverse] sync optimus
       '');
 
       prime.offload.enable = lib.mkEnableOption (lib.mdDoc ''
@@ -110,7 +168,7 @@ in {
         If this is enabled, then the bus IDs of the NVIDIA and Intel/AMD GPUs have to
         be specified ({option}`hardware.nvidia.prime.nvidiaBusId` and
         {option}`hardware.nvidia.prime.intelBusId` or
-        {option}`hardware.nvidia.prime.amdgpuBusId`).
+        {option}`hardware.nvidia.prime.amdgpuBusId`)
       '');
 
       prime.offload.enableOffloadCmd = lib.mkEnableOption (lib.mdDoc ''
@@ -118,7 +176,7 @@ in {
         for offloading programs to an nvidia device. To work, should have also enabled
         {option}`hardware.nvidia.prime.offload.enable` or {option}`hardware.nvidia.prime.reverseSync.enable`.
 
-        Example usage `nvidia-offload sauerbraten_client`.
+        Example usage `nvidia-offload sauerbraten_client`
       '');
 
       prime.reverseSync.enable = lib.mkEnableOption (lib.mdDoc ''
@@ -146,30 +204,36 @@ in {
 
         Note that this configuration will only be successful when a display manager
         for which the {option}`services.xserver.displayManager.setupCommands`
-        option is supported is used.
+        option is supported is used
       '');
 
       nvidiaSettings =
         (lib.mkEnableOption (lib.mdDoc ''
-          nvidia-settings, NVIDIA's GUI configuration tool.
+          nvidia-settings, NVIDIA's GUI configuration tool
         ''))
         // {default = true;};
 
       nvidiaPersistenced = lib.mkEnableOption (lib.mdDoc ''
         nvidia-persistenced a update for NVIDIA GPU headless mode, i.e.
-        It ensures all GPUs stay awake even during headless mode.
+        It ensures all GPUs stay awake even during headless mode
       '');
 
       forceFullCompositionPipeline = lib.mkEnableOption (lib.mdDoc ''
         forcefully 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.
+        It also drastically increases the time the driver needs to clock down after load
       '');
 
-      package = lib.mkPackageOptionMD config.boot.kernelPackages.nvidiaPackages "nvidia_x11" {
-        default = "stable";
+      package = lib.mkOption {
+        default = config.boot.kernelPackages.nvidiaPackages."${if cfg.datacenter.enable then "dc" else "stable"}";
+        defaultText = lib.literalExpression ''
+          config.boot.kernelPackages.nvidiaPackages."\$\{if cfg.datacenter.enable then "dc" else "stable"}"
+        '';
         example = lib.mdDoc "config.boot.kernelPackages.nvidiaPackages.legacy_470";
+        description = lib.mdDoc ''
+          The NVIDIA driver package to use.
+        '';
       };
 
       open = lib.mkEnableOption (lib.mdDoc ''
@@ -188,8 +252,55 @@ in {
       then pCfg.intelBusId
       else pCfg.amdgpuBusId;
   in
-    lib.mkIf (nvidia_x11 != null) {
-      assertions = [
+    lib.mkIf (nvidia_x11 != null) (lib.mkMerge [
+      # Common
+      ({
+        assertions = [
+          {
+            assertion = !(nvidiaEnabled && cfg.datacenter.enable);
+            message = "You cannot configure both X11 and Data Center drivers at the same time.";
+          }
+        ];
+        boot = {
+          blacklistedKernelModules = ["nouveau" "nvidiafb"];
+
+          # Don't add `nvidia-uvm` to `kernelModules`, because we want
+          # `nvidia-uvm` be loaded only after `udev` rules for `nvidia` kernel
+          # module are applied.
+          #
+          # Instead, we use `softdep` to lazily load `nvidia-uvm` kernel module
+          # after `nvidia` kernel module is loaded and `udev` rules are applied.
+          extraModprobeConfig = ''
+            softdep nvidia post: nvidia-uvm
+          '';
+        };
+        systemd.tmpfiles.rules =
+          lib.optional config.virtualisation.docker.enableNvidia
+            "L+ /run/nvidia-docker/bin - - - - ${nvidia_x11.bin}/origBin";
+        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 195 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 195 $${i}; done'"
+          KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c 195 254'"
+          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) 1'"
+        '';
+        hardware.opengl = {
+          extraPackages = [
+            nvidia_x11.out
+          ];
+          extraPackages32 = [
+            nvidia_x11.lib32
+          ];
+        };
+        environment.systemPackages = [
+          nvidia_x11.bin
+        ];
+      })
+      # X11
+      (lib.mkIf nvidiaEnabled {
+        assertions = [
         {
           assertion = primeEnabled -> pCfg.intelBusId == "" || pCfg.amdgpuBusId == "";
           message = "You cannot configure both an Intel iGPU and an AMD APU. Pick the one corresponding to your processor.";
@@ -248,227 +359,207 @@ in {
         {
           assertion = cfg.dynamicBoost.enable -> lib.versionAtLeast nvidia_x11.version "510.39.01";
           message = "NVIDIA's Dynamic Boost feature only exists on versions >= 510.39.01";
-        }
-      ];
-
-      # If Optimus/PRIME is enabled, we:
-      # - Specify the configured NVIDIA GPU bus ID in the Device section for the
-      #   "nvidia" driver.
-      # - Add the AllowEmptyInitialConfiguration option to the Screen section for the
-      #   "nvidia" driver, in order to allow the X server to start without any outputs.
-      # - Add a separate Device section for the Intel GPU, using the "modesetting"
-      #   driver and with the configured BusID.
-      # - OR add a separate Device section for the AMD APU, using the "amdgpu"
-      #   driver and with the configures BusID.
-      # - Reference that Device section from the ServerLayout section as an inactive
-      #   device.
-      # - Configure the display manager to run specific `xrandr` commands which will
-      #   configure/enable displays connected to the Intel iGPU / AMD APU.
-
-      # reverse sync implies offloading
-      hardware.nvidia.prime.offload.enable = lib.mkDefault reverseSyncCfg.enable;
-
-      services.xserver.drivers =
-        lib.optional primeEnabled {
-          name = igpuDriver;
-          display = offloadCfg.enable;
-          modules = lib.optional (igpuDriver == "amdgpu") pkgs.xorg.xf86videoamdgpu;
-          deviceSection =
-            ''
-              BusID "${igpuBusId}"
-            ''
-            + lib.optionalString (syncCfg.enable && igpuDriver != "amdgpu") ''
-              Option "AccelMethod" "none"
-            '';
-        }
-        ++ lib.singleton {
-          name = "nvidia";
-          modules = [nvidia_x11.bin];
-          display = !offloadCfg.enable;
-          deviceSection =
-            lib.optionalString primeEnabled
-            ''
-              BusID "${pCfg.nvidiaBusId}"
-            ''
-            + lib.optionalString pCfg.allowExternalGpu ''
-              Option "AllowExternalGpus"
-            '';
-          screenSection =
-            ''
-              Option "RandRRotation" "on"
-            ''
-            + lib.optionalString syncCfg.enable ''
-              Option "AllowEmptyInitialConfiguration"
-            ''
-            + lib.optionalString cfg.forceFullCompositionPipeline ''
-              Option         "metamodes" "nvidia-auto-select +0+0 {ForceFullCompositionPipeline=On}"
-              Option         "AllowIndirectGLXProtocol" "off"
-              Option         "TripleBuffer" "on"
-            '';
-        };
-
-      services.xserver.serverLayoutSection =
-        lib.optionalString syncCfg.enable ''
-          Inactive "Device-${igpuDriver}[0]"
-        ''
-        + lib.optionalString reverseSyncCfg.enable ''
-          Inactive "Device-nvidia[0]"
-        ''
-        + lib.optionalString offloadCfg.enable ''
-          Option "AllowNVIDIAGPUScreens"
-        '';
-
-      services.xserver.displayManager.setupCommands = let
-        gpuProviderName =
-          if igpuDriver == "amdgpu"
-          then
-            # find the name of the provider if amdgpu
-            "`${lib.getExe pkgs.xorg.xrandr} --listproviders | ${lib.getExe pkgs.gnugrep} -i AMD | ${lib.getExe pkgs.gnused} -n 's/^.*name://p'`"
-          else igpuDriver;
-        providerCmdParams =
-          if syncCfg.enable
-          then "\"${gpuProviderName}\" NVIDIA-0"
-          else "NVIDIA-G0 \"${gpuProviderName}\"";
-      in
-        lib.optionalString (syncCfg.enable || reverseSyncCfg.enable) ''
-          # Added by nvidia configuration module for Optimus/PRIME.
-          ${lib.getExe pkgs.xorg.xrandr} --setprovideroutputsource ${providerCmdParams}
-          ${lib.getExe pkgs.xorg.xrandr} --auto
-        '';
-
-      environment.etc = {
-        "nvidia/nvidia-application-profiles-rc" = lib.mkIf nvidia_x11.useProfiles {source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc";};
+        }];
+
+        # If Optimus/PRIME is enabled, we:
+        # - Specify the configured NVIDIA GPU bus ID in the Device section for the
+        #   "nvidia" driver.
+        # - Add the AllowEmptyInitialConfiguration option to the Screen section for the
+        #   "nvidia" driver, in order to allow the X server to start without any outputs.
+        # - Add a separate Device section for the Intel GPU, using the "modesetting"
+        #   driver and with the configured BusID.
+        # - OR add a separate Device section for the AMD APU, using the "amdgpu"
+        #   driver and with the configures BusID.
+        # - Reference that Device section from the ServerLayout section as an inactive
+        #   device.
+        # - Configure the display manager to run specific `xrandr` commands which will
+        #   configure/enable displays connected to the Intel iGPU / AMD APU.
+
+        # reverse sync implies offloading
+        hardware.nvidia.prime.offload.enable = lib.mkDefault reverseSyncCfg.enable;
+
+        services.xserver.drivers =
+          lib.optional primeEnabled {
+            name = igpuDriver;
+            display = offloadCfg.enable;
+            modules = lib.optional (igpuDriver == "amdgpu") pkgs.xorg.xf86videoamdgpu;
+            deviceSection =
+              ''
+                BusID "${igpuBusId}"
+              ''
+              + lib.optionalString (syncCfg.enable && igpuDriver != "amdgpu") ''
+                Option "AccelMethod" "none"
+              '';
+          }
+          ++ lib.singleton {
+            name = "nvidia";
+            modules = [nvidia_x11.bin];
+            display = !offloadCfg.enable;
+            deviceSection =
+              lib.optionalString primeEnabled
+              ''
+                BusID "${pCfg.nvidiaBusId}"
+              ''
+              + lib.optionalString pCfg.allowExternalGpu ''
+                Option "AllowExternalGpus"
+              '';
+            screenSection =
+              ''
+                Option "RandRRotation" "on"
+              ''
+              + lib.optionalString syncCfg.enable ''
+                Option "AllowEmptyInitialConfiguration"
+              ''
+              + lib.optionalString cfg.forceFullCompositionPipeline ''
+                Option         "metamodes" "nvidia-auto-select +0+0 {ForceFullCompositionPipeline=On}"
+                Option         "AllowIndirectGLXProtocol" "off"
+                Option         "TripleBuffer" "on"
+              '';
+          };
 
-        # 'nvidia_x11' installs it's files to /run/opengl-driver/...
-        "egl/egl_external_platform.d".source = "/run/opengl-driver/share/egl/egl_external_platform.d/";
-      };
+        services.xserver.serverLayoutSection =
+          lib.optionalString syncCfg.enable ''
+            Inactive "Device-${igpuDriver}[0]"
+          ''
+          + lib.optionalString reverseSyncCfg.enable ''
+            Inactive "Device-nvidia[0]"
+          ''
+          + lib.optionalString offloadCfg.enable ''
+            Option "AllowNVIDIAGPUScreens"
+          '';
+
+        services.xserver.displayManager.setupCommands = let
+          gpuProviderName =
+            if igpuDriver == "amdgpu"
+            then
+              # find the name of the provider if amdgpu
+              "`${lib.getExe pkgs.xorg.xrandr} --listproviders | ${lib.getExe pkgs.gnugrep} -i AMD | ${lib.getExe pkgs.gnused} -n 's/^.*name://p'`"
+            else igpuDriver;
+          providerCmdParams =
+            if syncCfg.enable
+            then "\"${gpuProviderName}\" NVIDIA-0"
+            else "NVIDIA-G0 \"${gpuProviderName}\"";
+        in
+          lib.optionalString (syncCfg.enable || reverseSyncCfg.enable) ''
+            # Added by nvidia configuration module for Optimus/PRIME.
+            ${lib.getExe pkgs.xorg.xrandr} --setprovideroutputsource ${providerCmdParams}
+            ${lib.getExe pkgs.xorg.xrandr} --auto
+          '';
+
+        environment.etc = {
+          "nvidia/nvidia-application-profiles-rc" = lib.mkIf nvidia_x11.useProfiles {source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc";};
+
+          # 'nvidia_x11' installs it's files to /run/opengl-driver/...
+          "egl/egl_external_platform.d".source = "/run/opengl-driver/share/egl/egl_external_platform.d/";
+        };
 
-      hardware.opengl = {
-        extraPackages = [
-          nvidia_x11.out
-          pkgs.nvidia-vaapi-driver
-        ];
-        extraPackages32 = [
-          nvidia_x11.lib32
-          pkgs.pkgsi686Linux.nvidia-vaapi-driver
-        ];
-      };
-      environment.systemPackages =
-        [nvidia_x11.bin]
-        ++ lib.optional cfg.nvidiaSettings nvidia_x11.settings
-        ++ lib.optional cfg.nvidiaPersistenced nvidia_x11.persistenced
-        ++ lib.optional offloadCfg.enableOffloadCmd
-        (pkgs.writeShellScriptBin "nvidia-offload" ''
-          export __NV_PRIME_RENDER_OFFLOAD=1
-          export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
-          export __GLX_VENDOR_LIBRARY_NAME=nvidia
-          export __VK_LAYER_NV_optimus=NVIDIA_only
-          exec "$@"
-        '');
-
-      systemd.packages = lib.optional cfg.powerManagement.enable nvidia_x11.out;
-
-      systemd.services = let
-        nvidiaService = state: {
-          description = "NVIDIA system ${state} actions";
-          path = [pkgs.kbd];
-          serviceConfig = {
-            Type = "oneshot";
-            ExecStart = "${nvidia_x11.out}/bin/nvidia-sleep.sh '${state}'";
-          };
-          before = ["systemd-${state}.service"];
-          requiredBy = ["systemd-${state}.service"];
+        hardware.opengl = {
+          extraPackages = [
+            pkgs.nvidia-vaapi-driver
+          ];
+          extraPackages32 = [
+            pkgs.pkgsi686Linux.nvidia-vaapi-driver
+          ];
         };
-      in
-        lib.mkMerge [
-          (lib.mkIf cfg.powerManagement.enable {
-            nvidia-suspend = nvidiaService "suspend";
-            nvidia-hibernate = nvidiaService "hibernate";
-            nvidia-resume =
-              (nvidiaService "resume")
-              // {
-                before = [];
-                after = ["systemd-suspend.service" "systemd-hibernate.service"];
-                requiredBy = ["systemd-suspend.service" "systemd-hibernate.service"];
-              };
-          })
-          (lib.mkIf cfg.nvidiaPersistenced {
-            "nvidia-persistenced" = {
-              description = "NVIDIA Persistence Daemon";
-              wantedBy = ["multi-user.target"];
-              serviceConfig = {
-                Type = "forking";
-                Restart = "always";
-                PIDFile = "/var/run/nvidia-persistenced/nvidia-persistenced.pid";
-                ExecStart = "${lib.getExe nvidia_x11.persistenced} --verbose";
-                ExecStopPost = "${pkgs.coreutils}/bin/rm -rf /var/run/nvidia-persistenced";
-              };
+        environment.systemPackages =
+          lib.optional cfg.nvidiaSettings nvidia_x11.settings
+          ++ lib.optional cfg.nvidiaPersistenced nvidia_x11.persistenced
+          ++ lib.optional offloadCfg.enableOffloadCmd
+          (pkgs.writeShellScriptBin "nvidia-offload" ''
+            export __NV_PRIME_RENDER_OFFLOAD=1
+            export __NV_PRIME_RENDER_OFFLOAD_PROVIDER=NVIDIA-G0
+            export __GLX_VENDOR_LIBRARY_NAME=nvidia
+            export __VK_LAYER_NV_optimus=NVIDIA_only
+            exec "$@"
+          '');
+
+        systemd.packages = lib.optional cfg.powerManagement.enable nvidia_x11.out;
+
+        systemd.services = let
+          nvidiaService = state: {
+            description = "NVIDIA system ${state} actions";
+            path = [pkgs.kbd];
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = "${nvidia_x11.out}/bin/nvidia-sleep.sh '${state}'";
             };
-          })
-          (lib.mkIf cfg.dynamicBoost.enable {
-            "nvidia-powerd" = {
-              description = "nvidia-powerd service";
-              path = [
-                pkgs.util-linux # nvidia-powerd wants lscpu
-              ];
-              wantedBy = ["multi-user.target"];
-              serviceConfig = {
-                Type = "dbus";
-                BusName = "nvidia.powerd.server";
-                ExecStart = "${nvidia_x11.bin}/bin/nvidia-powerd";
+            before = ["systemd-${state}.service"];
+            requiredBy = ["systemd-${state}.service"];
+          };
+        in
+          lib.mkMerge [
+            (lib.mkIf cfg.powerManagement.enable {
+              nvidia-suspend = nvidiaService "suspend";
+              nvidia-hibernate = nvidiaService "hibernate";
+              nvidia-resume =
+                (nvidiaService "resume")
+                // {
+                  before = [];
+                  after = ["systemd-suspend.service" "systemd-hibernate.service"];
+                  requiredBy = ["systemd-suspend.service" "systemd-hibernate.service"];
+                };
+            })
+            (lib.mkIf cfg.nvidiaPersistenced {
+              "nvidia-persistenced" = {
+                description = "NVIDIA Persistence Daemon";
+                wantedBy = ["multi-user.target"];
+                serviceConfig = {
+                  Type = "forking";
+                  Restart = "always";
+                  PIDFile = "/var/run/nvidia-persistenced/nvidia-persistenced.pid";
+                  ExecStart = "${lib.getExe nvidia_x11.persistenced} --verbose";
+                  ExecStopPost = "${pkgs.coreutils}/bin/rm -rf /var/run/nvidia-persistenced";
+                };
               };
-            };
-          })
-        ];
-
-      services.acpid.enable = true;
-
-      services.dbus.packages = lib.optional cfg.dynamicBoost.enable nvidia_x11.bin;
-
-      hardware.firmware = lib.optional cfg.open nvidia_x11.firmware;
-
-      systemd.tmpfiles.rules =
-        lib.optional config.virtualisation.docker.enableNvidia
-        "L+ /run/nvidia-docker/bin - - - - ${nvidia_x11.bin}/origBin"
-        ++ lib.optional (nvidia_x11.persistenced != null && config.virtualisation.docker.enableNvidia)
-        "L+ /run/nvidia-docker/extras/bin/nvidia-persistenced - - - - ${nvidia_x11.persistenced}/origBin/nvidia-persistenced";
-
-      boot = {
-        blacklistedKernelModules = ["nouveau" "nvidiafb"];
-
-        extraModulePackages =
-          if cfg.open
-          then [nvidia_x11.open]
-          else [nvidia_x11.bin];
-
-        # nvidia-uvm is required by CUDA applications.
-        kernelModules =
-          ["nvidia-uvm"]
-          ++ lib.optionals config.services.xserver.enable ["nvidia" "nvidia_modeset" "nvidia_drm"];
-
-        # If requested enable modesetting via kernel parameter.
-        kernelParams =
-          lib.optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
-          ++ lib.optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
-          ++ lib.optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
-          ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2" && !ibtSupport) "ibt=off";
-
-        # enable finegrained power management
-        extraModprobeConfig = lib.optionalString cfg.powerManagement.finegrained ''
-          options nvidia "NVreg_DynamicPowerManagement=0x02"
-        '';
-      };
-
-      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=="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) 1'"
-        ''
-        + lib.optionalString cfg.powerManagement.finegrained (
+            })
+            (lib.mkIf cfg.dynamicBoost.enable {
+              "nvidia-powerd" = {
+                description = "nvidia-powerd service";
+                path = [
+                  pkgs.util-linux # nvidia-powerd wants lscpu
+                ];
+                wantedBy = ["multi-user.target"];
+                serviceConfig = {
+                  Type = "dbus";
+                  BusName = "nvidia.powerd.server";
+                  ExecStart = "${nvidia_x11.bin}/bin/nvidia-powerd";
+                };
+              };
+            })
+          ];
+        services.acpid.enable = true;
+
+        services.dbus.packages = lib.optional cfg.dynamicBoost.enable nvidia_x11.bin;
+
+        hardware.firmware = lib.optional cfg.open nvidia_x11.firmware;
+
+        systemd.tmpfiles.rules =
+          lib.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 =
+            if cfg.open
+            then [nvidia_x11.open]
+            else [nvidia_x11.bin];
+          # nvidia-uvm is required by CUDA applications.
+          kernelModules =
+            lib.optionals config.services.xserver.enable ["nvidia" "nvidia_modeset" "nvidia_drm"];
+
+          # If requested enable modesetting via kernel parameter.
+          kernelParams =
+            lib.optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
+            ++ lib.optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
+            ++ lib.optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
+            ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2" && !ibtSupport) "ibt=off";
+
+          # enable finegrained power management
+          extraModprobeConfig = lib.optionalString cfg.powerManagement.finegrained ''
+            options nvidia "NVreg_DynamicPowerManagement=0x02"
+          '';
+        };
+        services.udev.extraRules =
+          lib.optionalString cfg.powerManagement.finegrained (
           lib.optionalString (lib.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"
@@ -489,5 +580,56 @@ in {
             ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on"
           ''
         );
-    };
+      })
+      # Data Center
+      (lib.mkIf (cfg.datacenter.enable) {
+        boot.extraModulePackages = [
+          nvidia_x11.bin
+        ];
+
+        systemd = {
+          tmpfiles.rules =
+            lib.optional (nvidia_x11.persistenced != null && config.virtualisation.docker.enableNvidia)
+            "L+ /run/nvidia-docker/extras/bin/nvidia-persistenced - - - - ${nvidia_x11.persistenced}/origBin/nvidia-persistenced";
+
+          services = lib.mkMerge [
+            ({
+              nvidia-fabricmanager = {
+                enable = true;
+                description = "Start NVIDIA NVLink Management";
+                wantedBy = [ "multi-user.target" ];
+                unitConfig.After = [ "network-online.target" ];
+                unitConfig.Requires = [ "network-online.target" ];
+                serviceConfig = {
+                  Type = "forking";
+                  TimeoutStartSec = 240;
+                  ExecStart = let
+                    nv-fab-conf = settingsFormat.generate "fabricmanager.conf" cfg.datacenter.settings;
+                    in
+                      "${lib.getExe nvidia_x11.fabricmanager} -c ${nv-fab-conf}";
+                  LimitCORE="infinity";
+                };
+              };
+            })
+            (lib.mkIf cfg.nvidiaPersistenced {
+              "nvidia-persistenced" = {
+                description = "NVIDIA Persistence Daemon";
+                wantedBy = ["multi-user.target"];
+                serviceConfig = {
+                  Type = "forking";
+                  Restart = "always";
+                  PIDFile = "/var/run/nvidia-persistenced/nvidia-persistenced.pid";
+                  ExecStart = "${lib.getExe nvidia_x11.persistenced} --verbose";
+                  ExecStopPost = "${pkgs.coreutils}/bin/rm -rf /var/run/nvidia-persistenced";
+                };
+              };
+            })
+          ];
+      };
+
+      environment.systemPackages =
+        lib.optional cfg.datacenter.enable nvidia_x11.fabricmanager
+        ++ lib.optional cfg.nvidiaPersistenced nvidia_x11.persistenced;
+    })
+  ]);
 }
diff --git a/nixos/modules/hardware/video/webcam/facetimehd.nix b/nixos/modules/hardware/video/webcam/facetimehd.nix
index 480c636aa0d9d..a0ec9c98a54c9 100644
--- a/nixos/modules/hardware/video/webcam/facetimehd.nix
+++ b/nixos/modules/hardware/video/webcam/facetimehd.nix
@@ -12,7 +12,7 @@ in
 
 {
 
-  options.hardware.facetimehd.enable = mkEnableOption (lib.mdDoc "facetimehd kernel module");
+  options.hardware.facetimehd.enable = mkEnableOption (lib.mdDoc "the facetimehd kernel module");
 
   options.hardware.facetimehd.withCalibration = mkOption {
     default = false;
diff --git a/nixos/modules/hardware/video/webcam/ipu6.nix b/nixos/modules/hardware/video/webcam/ipu6.nix
index fce78cda34c71..c2dbdc217bd60 100644
--- a/nixos/modules/hardware/video/webcam/ipu6.nix
+++ b/nixos/modules/hardware/video/webcam/ipu6.nix
@@ -13,11 +13,12 @@ in
     enable = mkEnableOption (lib.mdDoc "support for Intel IPU6/MIPI cameras");
 
     platform = mkOption {
-      type = types.enum [ "ipu6" "ipu6ep" ];
+      type = types.enum [ "ipu6" "ipu6ep" "ipu6epmtl" ];
       description = lib.mdDoc ''
         Choose the version for your hardware platform.
 
-        Use `ipu6` for Tiger Lake and `ipu6ep` for Alder Lake respectively.
+        Use `ipu6` for Tiger Lake, `ipu6ep` for Alder Lake or Raptor Lake,
+        and `ipu6epmtl` for Meteor Lake.
       '';
     };
 
@@ -29,9 +30,7 @@ in
       ipu6-drivers
     ];
 
-    hardware.firmware = with pkgs; [ ]
-      ++ optional (cfg.platform == "ipu6") ipu6-camera-bin
-      ++ optional (cfg.platform == "ipu6ep") ipu6ep-camera-bin;
+    hardware.firmware = [ pkgs.ipu6-camera-bins ];
 
     services.udev.extraRules = ''
       SUBSYSTEM=="intel-ipu6-psys", MODE="0660", GROUP="video"
@@ -44,14 +43,13 @@ in
 
       extraPackages = with pkgs.gst_all_1; [ ]
         ++ optional (cfg.platform == "ipu6") icamerasrc-ipu6
-        ++ optional (cfg.platform == "ipu6ep") icamerasrc-ipu6ep;
+        ++ optional (cfg.platform == "ipu6ep") icamerasrc-ipu6ep
+        ++ optional (cfg.platform == "ipu6epmtl") icamerasrc-ipu6epmtl;
 
       input = {
         pipeline = "icamerasrc";
-        format = mkIf (cfg.platform == "ipu6ep") (mkDefault "NV12");
+        format = mkIf (cfg.platform != "ipu6") (mkDefault "NV12");
       };
     };
-
   };
-
 }
diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix
index 3d52c08888eae..530727f3f2928 100644
--- a/nixos/modules/i18n/input-method/fcitx5.nix
+++ b/nixos/modules/i18n/input-method/fcitx5.nix
@@ -19,6 +19,14 @@ in
           Enabled Fcitx5 addons.
         '';
       };
+      waylandFrontend = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Use the Wayland input method frontend.
+          See [Using Fcitx 5 on Wayland](https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland).
+        '';
+      };
       quickPhrase = mkOption {
         type = with types; attrsOf str;
         default = { };
@@ -118,10 +126,11 @@ in
       ];
 
     environment.variables = {
-      GTK_IM_MODULE = "fcitx";
-      QT_IM_MODULE = "fcitx";
       XMODIFIERS = "@im=fcitx";
       QT_PLUGIN_PATH = [ "${fcitx5Package}/${pkgs.qt6.qtbase.qtPluginPrefix}" ];
+    } // lib.optionalAttrs (!cfg.waylandFrontend) {
+      GTK_IM_MODULE = "fcitx";
+      QT_IM_MODULE = "fcitx";
     } // lib.optionalAttrs cfg.ignoreUserConfig {
       SKIP_FCITX_USER_PATH = "1";
     };
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index 2a35afad2ac76..a81ce828b13db 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -47,7 +47,7 @@ in
       panel = mkOption {
         type = with types; nullOr path;
         default = null;
-        example = literalExpression ''"''${pkgs.plasma5Packages.plasma-desktop}/lib/libexec/kimpanel-ibus-panel"'';
+        example = literalExpression ''"''${pkgs.plasma5Packages.plasma-desktop}/libexec/kimpanel-ibus-panel"'';
         description = lib.mdDoc "Replace the IBus panel with another panel.";
       };
     };
diff --git a/nixos/modules/image/repart-image.nix b/nixos/modules/image/repart-image.nix
new file mode 100644
index 0000000000000..a12b4fb14fb16
--- /dev/null
+++ b/nixos/modules/image/repart-image.nix
@@ -0,0 +1,110 @@
+# This is an expression meant to be called from `./repart.nix`, it is NOT a
+# NixOS module that can be imported.
+
+{ lib
+, runCommand
+, python3
+, black
+, ruff
+, mypy
+, systemd
+, fakeroot
+, util-linux
+
+  # filesystem tools
+, dosfstools
+, mtools
+, e2fsprogs
+, squashfsTools
+, erofs-utils
+, btrfs-progs
+, xfsprogs
+
+  # compression tools
+, zstd
+, xz
+
+  # arguments
+, imageFileBasename
+, compression
+, fileSystems
+, partitions
+, split
+, seed
+, definitionsDirectory
+}:
+
+let
+  amendRepartDefinitions = runCommand "amend-repart-definitions.py"
+    {
+      # TODO: ruff does not splice properly in nativeBuildInputs
+      depsBuildBuild = [ ruff ];
+      nativeBuildInputs = [ python3 black mypy ];
+    } ''
+    install ${./amend-repart-definitions.py} $out
+    patchShebangs --build $out
+
+    black --check --diff $out
+    ruff --line-length 88 $out
+    mypy --strict $out
+  '';
+
+  fileSystemToolMapping = {
+    "vfat" = [ dosfstools mtools ];
+    "ext4" = [ e2fsprogs.bin ];
+    "squashfs" = [ squashfsTools ];
+    "erofs" = [ erofs-utils ];
+    "btrfs" = [ btrfs-progs ];
+    "xfs" = [ xfsprogs ];
+  };
+
+  fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems;
+
+  compressionPkg = {
+    "zstd" = zstd;
+    "xz" = xz;
+  }."${compression.algorithm}";
+
+  compressionCommand = {
+    "zstd" = "zstd --no-progress --threads=0 -${toString compression.level}";
+    "xz" = "xz --keep --verbose --threads=0 -${toString compression.level}";
+  }."${compression.algorithm}";
+in
+
+runCommand imageFileBasename
+{
+  nativeBuildInputs = [
+    systemd
+    fakeroot
+    util-linux
+    compressionPkg
+  ] ++ fileSystemTools;
+} ''
+  amendedRepartDefinitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory})
+
+  mkdir -p $out
+  cd $out
+
+  echo "Building image with systemd-repart..."
+  unshare --map-root-user fakeroot systemd-repart \
+    --dry-run=no \
+    --empty=create \
+    --size=auto \
+    --seed="${seed}" \
+    --definitions="$amendedRepartDefinitions" \
+    --split="${lib.boolToString split}" \
+    --json=pretty \
+    ${imageFileBasename}.raw \
+    | tee repart-output.json
+
+  # Compression is implemented in the same derivation as opposed to in a
+  # separate derivation to allow users to save disk space. Disk images are
+  # already very space intensive so we want to allow users to mitigate this.
+  if ${lib.boolToString compression.enable}; then
+    for f in ${imageFileBasename}*; do
+      echo "Compressing $f with ${compression.algorithm}..."
+      # Keep the original file when compressing and only delete it afterwards
+      ${compressionCommand} $f && rm $f
+    done
+  fi
+''
diff --git a/nixos/modules/image/repart.nix b/nixos/modules/image/repart.nix
index 4a0021e9a56e5..ed584d9bf997b 100644
--- a/nixos/modules/image/repart.nix
+++ b/nixos/modules/image/repart.nix
@@ -34,12 +34,13 @@ let
           };
         });
         default = { };
-        example = lib.literalExpression '' {
-          "/EFI/BOOT/BOOTX64.EFI".source =
-            "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
+        example = lib.literalExpression ''
+          {
+            "/EFI/BOOT/BOOTX64.EFI".source =
+              "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
 
-          "/loader/entries/nixos.conf".source = systemdBootEntry;
-        }
+            "/loader/entries/nixos.conf".source = systemdBootEntry;
+          }
         '';
         description = lib.mdDoc "The contents to end up in the filesystem image.";
       };
@@ -65,7 +66,53 @@ in
 
     name = lib.mkOption {
       type = lib.types.str;
-      description = lib.mdDoc "The name of the image.";
+      description = lib.mdDoc ''
+        Name of the image.
+
+        If this option is unset but config.system.image.id is set,
+        config.system.image.id is used as the default value.
+      '';
+    };
+
+    version = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = config.system.image.version;
+      defaultText = lib.literalExpression "config.system.image.version";
+      description = lib.mdDoc "Version of the image";
+    };
+
+    imageFileBasename = lib.mkOption {
+      type = lib.types.str;
+      readOnly = true;
+      description = lib.mdDoc ''
+        Basename of the image filename without any extension (e.g. `image_1`).
+      '';
+    };
+
+    imageFile = lib.mkOption {
+      type = lib.types.str;
+      readOnly = true;
+      description = lib.mdDoc ''
+        Filename of the image including all extensions (e.g `image_1.raw` or
+        `image_1.raw.zst`).
+      '';
+    };
+
+    compression = {
+      enable = lib.mkEnableOption (lib.mdDoc "Image compression");
+
+      algorithm = lib.mkOption {
+        type = lib.types.enum [ "zstd" "xz" ];
+        default = "zstd";
+        description = lib.mdDoc "Compression algorithm";
+      };
+
+      level = lib.mkOption {
+        type = lib.types.int;
+        description = lib.mdDoc ''
+          Compression level. The available range depends on the used algorithm.
+        '';
+      };
     };
 
     seed = lib.mkOption {
@@ -89,35 +136,36 @@ in
     };
 
     package = lib.mkPackageOption pkgs "systemd-repart" {
-      default = "systemd";
-      example = lib.literalExpression ''
-        pkgs.systemdMinimal.override { withCryptsetup = true; }
-      '';
+      # We use buildPackages so that repart images are built with the build
+      # platform's systemd, allowing for cross-compiled systems to work.
+      default = [ "buildPackages" "systemd" ];
+      example = "pkgs.buildPackages.systemdMinimal.override { withCryptsetup = true; }";
     };
 
     partitions = lib.mkOption {
       type = with lib.types; attrsOf (submodule partitionOptions);
       default = { };
-      example = lib.literalExpression '' {
-        "10-esp" = {
-          contents = {
-            "/EFI/BOOT/BOOTX64.EFI".source =
-              "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
-          }
-          repartConfig = {
-            Type = "esp";
-            Format = "fat";
+      example = lib.literalExpression ''
+        {
+          "10-esp" = {
+            contents = {
+              "/EFI/BOOT/BOOTX64.EFI".source =
+                "''${pkgs.systemd}/lib/systemd/boot/efi/systemd-bootx64.efi";
+            }
+            repartConfig = {
+              Type = "esp";
+              Format = "fat";
+            };
           };
-        };
-        "20-root" = {
-          storePaths = [ config.system.build.toplevel ];
-          repartConfig = {
-            Type = "root";
-            Format = "ext4";
-            Minimize = "guess";
+          "20-root" = {
+            storePaths = [ config.system.build.toplevel ];
+            repartConfig = {
+              Type = "root";
+              Format = "ext4";
+              Minimize = "guess";
+            };
           };
         };
-      };
       '';
       description = lib.mdDoc ''
         Specify partitions as a set of the names of the partitions with their
@@ -129,24 +177,38 @@ in
 
   config = {
 
-    system.build.image =
+    image.repart =
       let
-        fileSystemToolMapping = with pkgs; {
-          "vfat" = [ dosfstools mtools ];
-          "ext4" = [ e2fsprogs.bin ];
-          "squashfs" = [ squashfsTools ];
-          "erofs" = [ erofs-utils ];
-          "btrfs" = [ btrfs-progs ];
-          "xfs" = [ xfsprogs ];
+        version = config.image.repart.version;
+        versionInfix = if version != null then "_${version}" else "";
+        compressionSuffix = lib.optionalString cfg.compression.enable
+          {
+            "zstd" = ".zst";
+            "xz" = ".xz";
+          }."${cfg.compression.algorithm}";
+      in
+      {
+        name = lib.mkIf (config.system.image.id != null) (lib.mkOptionDefault config.system.image.id);
+        imageFileBasename = cfg.name + versionInfix;
+        imageFile = cfg.imageFileBasename + ".raw" + compressionSuffix;
+
+        compression = {
+          # Generally default to slightly faster than default compression
+          # levels under the assumption that most of the building will be done
+          # for development and release builds will be customized.
+          level = lib.mkOptionDefault {
+            "zstd" = 3;
+            "xz" = 3;
+          }."${cfg.compression.algorithm}";
         };
+      };
 
+    system.build.image =
+      let
         fileSystems = lib.filter
           (f: f != null)
           (lib.mapAttrsToList (_n: v: v.repartConfig.Format or null) cfg.partitions);
 
-        fileSystemTools = builtins.concatMap (f: fileSystemToolMapping."${f}") fileSystems;
-
-
         makeClosure = paths: pkgs.closureInfo { rootPaths = paths; };
 
         # Add the closure of the provided Nix store paths to cfg.partitions so
@@ -157,23 +219,8 @@ in
             { closure = "${makeClosure partitionConfig.storePaths}/store-paths"; }
         );
 
-
         finalPartitions = lib.mapAttrs addClosure cfg.partitions;
 
-
-        amendRepartDefinitions = pkgs.runCommand "amend-repart-definitions.py"
-          {
-            nativeBuildInputs = with pkgs; [ black ruff mypy ];
-            buildInputs = [ pkgs.python3 ];
-          } ''
-          install ${./amend-repart-definitions.py} $out
-          patchShebangs --host $out
-
-          black --check --diff $out
-          ruff --line-length 88 $out
-          mypy --strict $out
-        '';
-
         format = pkgs.formats.ini { };
 
         definitionsDirectory = utils.systemdUtils.lib.definitions
@@ -183,34 +230,13 @@ in
 
         partitions = pkgs.writeText "partitions.json" (builtins.toJSON finalPartitions);
       in
-      pkgs.runCommand cfg.name
-        {
-          nativeBuildInputs = [
-            cfg.package
-            pkgs.fakeroot
-          ] ++ fileSystemTools;
-        } ''
-        amendedRepartDefinitions=$(${amendRepartDefinitions} ${partitions} ${definitionsDirectory})
-
-        mkdir -p $out
-        cd $out
-
-        fakeroot systemd-repart \
-          --dry-run=no \
-          --empty=create \
-          --size=auto \
-          --seed="${cfg.seed}" \
-          --definitions="$amendedRepartDefinitions" \
-          --split="${lib.boolToString cfg.split}" \
-          --json=pretty \
-          image.raw \
-          | tee repart-output.json
-      '';
+      pkgs.callPackage ./repart-image.nix {
+        systemd = cfg.package;
+        inherit (cfg) imageFileBasename compression split seed;
+        inherit fileSystems definitionsDirectory partitions;
+      };
 
-    meta = {
-      maintainers = with lib.maintainers; [ nikstur ];
-      doc = ./repart.md;
-    };
+    meta.maintainers = with lib.maintainers; [ nikstur ];
 
   };
 }
diff --git a/nixos/modules/installer/cd-dvd/channel.nix b/nixos/modules/installer/cd-dvd/channel.nix
index 8426ba8fac000..bc70dc985fe00 100644
--- a/nixos/modules/installer/cd-dvd/channel.nix
+++ b/nixos/modules/installer/cd-dvd/channel.nix
@@ -3,8 +3,6 @@
 
 { config, lib, pkgs, ... }:
 
-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.
@@ -27,38 +25,40 @@ let
       if [ ! -e $out/nixos/nixpkgs ]; then
         ln -s . $out/nixos/nixpkgs
       fi
-      ${optionalString (config.system.nixos.revision != null) ''
+      ${lib.optionalString (config.system.nixos.revision != null) ''
         echo -n ${config.system.nixos.revision} > $out/nixos/.git-revision
       ''}
       echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
       echo ${config.system.nixos.versionSuffix} | sed -e s/pre// > $out/nixos/svn-revision
     '';
-
 in
 
 {
-  # Pin the nixpkgs flake in the installer to our cleaned up nixpkgs source.
-  # FIXME: this might be surprising and is really only needed for offline installations,
-  # see discussion in https://github.com/NixOS/nixpkgs/pull/204178#issuecomment-1336289021
-  nix.registry.nixpkgs.to = {
-    type = "path";
-    path = "${channelSources}/nixos";
-  };
+  options.system.installer.channel.enable = (lib.mkEnableOption "bundling NixOS/Nixpkgs channel in the installer") // { default = true; };
+  config = lib.mkIf config.system.installer.channel.enable {
+    # Pin the nixpkgs flake in the installer to our cleaned up nixpkgs source.
+    # FIXME: this might be surprising and is really only needed for offline installations,
+    # see discussion in https://github.com/NixOS/nixpkgs/pull/204178#issuecomment-1336289021
+    nix.registry.nixpkgs.to = {
+      type = "path";
+      path = "${channelSources}/nixos";
+    };
 
-  # Provide the NixOS/Nixpkgs sources in /etc/nixos.  This is required
-  # for nixos-install.
-  boot.postBootCommands = mkAfter
-    ''
-      if ! [ -e /var/lib/nixos/did-channel-init ]; then
-        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 \
-          ${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
-        touch /var/lib/nixos/did-channel-init
-      fi
-    '';
+    # Provide the NixOS/Nixpkgs sources in /etc/nixos.  This is required
+    # for nixos-install.
+    boot.postBootCommands = lib.mkAfter
+      ''
+        if ! [ -e /var/lib/nixos/did-channel-init ]; then
+          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 \
+            ${lib.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
+          touch /var/lib/nixos/did-channel-init
+        fi
+      '';
+  };
 }
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 ea8056ff870c5..573b31b439c2d 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
@@ -6,7 +6,6 @@
   imports = [ ./installation-cd-graphical-base.nix ];
 
   isoImage.edition = "gnome";
-  isoImage.graphicalGrub = true;
 
   services.xserver.desktopManager.gnome = {
     # Add Firefox and other tools useful for installation to the launcher
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
index 29afdd4710917..1932f90d4c360 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
@@ -18,7 +18,7 @@
   # not including it may cause annoying cache misses in the case of the NixOS manual.
   documentation.doc.enable = lib.mkOverride 500 true;
 
-  fonts.fontconfig.enable = lib.mkForce false;
+  fonts.fontconfig.enable = lib.mkOverride 500 false;
 
-  isoImage.edition = lib.mkForce "minimal";
+  isoImage.edition = lib.mkOverride 500 "minimal";
 }
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index c430048d6598f..6adb94e09aff3 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -24,6 +24,9 @@ let
         # Name appended to menuentry defaults to params if no specific name given.
         option.name or (optionalString (option ? params) "(${option.params})")
         }' ${optionalString (option ? class) " --class ${option.class}"} {
+          # Fallback to UEFI console for boot, efifb sometimes has difficulties.
+          terminal_output console
+
           linux ${defaults.image} \''${isoboot} ${defaults.params} ${
             option.params or ""
           }
@@ -185,33 +188,25 @@ let
       # So instead we'll list a lot of possibly valid modes :/
       #"3840x2160"
       #"2560x1440"
+      "1920x1200"
       "1920x1080"
       "1366x768"
+      "1280x800"
       "1280x720"
+      "1200x1920"
       "1024x768"
+      "800x1280"
       "800x600"
       "auto"
     ]}
 
-    # Fonts can be loaded?
-    # (This font is assumed to always be provided as a fallback by NixOS)
-    if loadfont (\$root)/EFI/boot/unicode.pf2; then
-      set with_fonts=true
-    fi
-    if [ "\$textmode" != "true" -a "\$with_fonts" == "true" ]; then
-      # Use graphical term, it can be either with background image or a theme.
-      # input is "console", while output is "gfxterm".
-      # This enables "serial" input and output only when possible.
-      # Otherwise the failure mode is to not even enable gfxterm.
-      if test "\$with_serial" == "yes"; then
-        terminal_output gfxterm serial
-        terminal_input  console serial
-      else
-        terminal_output gfxterm
-        terminal_input  console
-      fi
+    if [ "\$textmode" == "false" ]; then
+      terminal_output gfxterm
+      terminal_input  console
     else
-      # Sets colors for the non-graphical term.
+      terminal_output console
+      terminal_input  console
+      # Sets colors for console term.
       set menu_color_normal=cyan/blue
       set menu_color_highlight=white/blue
     fi
@@ -250,18 +245,58 @@ let
     touch $out/EFI/nixos-installer-image
 
     # ALWAYS required modules.
-    MODULES="fat iso9660 part_gpt part_msdos \
-             normal boot linux configfile loopback chain halt \
-             efifwsetup efi_gop \
-             ls search search_label search_fs_uuid search_fs_file \
-             gfxmenu gfxterm gfxterm_background gfxterm_menu test all_video loadenv \
-             exfat ext2 ntfs btrfs hfsplus udf \
-             videoinfo png \
-             echo serial \
-            "
+    MODULES=(
+      # Basic modules for filesystems and partition schemes
+      "fat"
+      "iso9660"
+      "part_gpt"
+      "part_msdos"
+
+      # Basic stuff
+      "normal"
+      "boot"
+      "linux"
+      "configfile"
+      "loopback"
+      "chain"
+      "halt"
+
+      # Allows rebooting into firmware setup interface
+      "efifwsetup"
+
+      # EFI Graphics Output Protocol
+      "efi_gop"
+
+      # User commands
+      "ls"
+
+      # System commands
+      "search"
+      "search_label"
+      "search_fs_uuid"
+      "search_fs_file"
+      "echo"
+
+      # We're not using it anymore, but we'll leave it in so it can be used
+      # by user, with the console using "C"
+      "serial"
+
+      # Graphical mode stuff
+      "gfxmenu"
+      "gfxterm"
+      "gfxterm_background"
+      "gfxterm_menu"
+      "test"
+      "loadenv"
+      "all_video"
+      "videoinfo"
+
+      # File types for graphical mode
+      "png"
+    )
 
     echo "Building GRUB with modules:"
-    for mod in $MODULES; do
+    for mod in ''${MODULES[@]}; do
       echo " - $mod"
     done
 
@@ -270,31 +305,27 @@ let
     for mod in efi_uga; do
       if [ -f ${grubPkgs.grub2_efi}/lib/grub/${grubPkgs.grub2_efi.grubTarget}/$mod.mod ]; then
         echo " - $mod"
-        MODULES+=" $mod"
+        MODULES+=("$mod")
       fi
     done
 
     # Make our own efi program, we can't rely on "grub-install" since it seems to
     # probe for devices, even with --skip-fs-probe.
-    grub-mkimage --directory=${grubPkgs.grub2_efi}/lib/grub/${grubPkgs.grub2_efi.grubTarget} -o $out/EFI/boot/boot${targetArch}.efi -p /EFI/boot -O ${grubPkgs.grub2_efi.grubTarget} \
-      $MODULES
+    grub-mkimage \
+      --directory=${grubPkgs.grub2_efi}/lib/grub/${grubPkgs.grub2_efi.grubTarget} \
+      -o $out/EFI/boot/boot${targetArch}.efi \
+      -p /EFI/boot \
+      -O ${grubPkgs.grub2_efi.grubTarget} \
+      ''${MODULES[@]}
     cp ${grubPkgs.grub2_efi}/share/grub/unicode.pf2 $out/EFI/boot/
 
     cat <<EOF > $out/EFI/boot/grub.cfg
 
-    set with_fonts=false
-    set textmode=${boolToString (!config.isoImage.graphicalGrub)}
-    # If you want to use serial for "terminal_*" commands, you need to set one up:
-    #   Example manual configuration:
-    #    → serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
-    # This uses the defaults, and makes the serial terminal available.
-    set with_serial=no
-    if serial; then set with_serial=yes ;fi
-    export with_serial
-    clear
+    set textmode=${boolToString (config.isoImage.forceTextMode)}
     set timeout=${toString grubEfiTimeout}
 
-    # This message will only be viewable when "gfxterm" is not used.
+    clear
+    # This message will only be viewable on the default (UEFI) console.
     echo ""
     echo "Loading graphical boot menu..."
     echo ""
@@ -306,7 +337,7 @@ let
     hiddenentry 'Text mode' --hotkey 't' {
       loadfont (\$root)/EFI/boot/unicode.pf2
       set textmode=true
-      terminal_output gfxterm console
+      terminal_output console
     }
     hiddenentry 'GUI mode' --hotkey 'g' {
       $(find ${config.isoImage.grubTheme} -iname '*.pf2' -printf "loadfont (\$root)/EFI/boot/grub-theme/%P\n")
@@ -400,6 +431,8 @@ let
     }
     EOF
 
+    grub-script-check $out/EFI/boot/grub.cfg
+
     ${refind}
   '';
 
@@ -479,9 +512,10 @@ in
                 + lib.optionalString isAarch "-Xbcj arm"
                 + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc"
                 + lib.optionalString (isSparc) "-Xbcj sparc";
-      type = lib.types.str;
+      type = lib.types.nullOr lib.types.str;
       description = lib.mdDoc ''
         Compression settings to use for the squashfs nix store.
+        `null` disables compression.
       '';
       example = "zstd -Xcompression-level 6";
     };
@@ -658,13 +692,17 @@ in
       '';
     };
 
-    isoImage.graphicalGrub = mkOption {
+    isoImage.forceTextMode = mkOption {
       default = false;
       type = types.bool;
       example = true;
       description = lib.mdDoc ''
-        Whether to use textmode or graphical grub.
-        false means we use textmode grub.
+        Whether to use text mode instead of graphical grub.
+        A value of `true` means graphical mode is not tried to be used.
+
+        This is useful for validating that graphics mode usage is not at the root cause of a problem with the iso image.
+
+        If text mode is required off-handedly (e.g. for serial use) you can use the `T` key, after being prompted, to use text mode for the current boot.
       '';
     };
 
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 10c37a46fdac1..e4241e9654036 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/3wqasl97rjiza3vd7fxjnvli2w9l30mk-nix-2.17.0";
-  i686-linux = "/nix/store/z360xswxfx55pmm1fng3hw748rbs0kkj-nix-2.17.0";
-  aarch64-linux = "/nix/store/9670sxa916xmv8n1kqs7cdvmnsrhrdjv-nix-2.17.0";
-  x86_64-darwin = "/nix/store/2rdbky9j8hc3mbgl6pnda4hkjllyfwnn-nix-2.17.0";
-  aarch64-darwin = "/nix/store/jl9qma14fb4zk9lq1k0syw2k9qm2gqjw-nix-2.17.0";
+  x86_64-linux = "/nix/store/azvn85cras6xv4z5j85fiy406f24r1q0-nix-2.18.1";
+  i686-linux = "/nix/store/9bnwy7f9h0kzdzmcnjjsjg0aak5waj40-nix-2.18.1";
+  aarch64-linux = "/nix/store/hh65xwqm9s040s3cgn9vzcmrxj0sf5ij-nix-2.18.1";
+  x86_64-darwin = "/nix/store/6zi5fqzn9n17wrk8r41rhdw4j7jqqsi3-nix-2.18.1";
+  aarch64-darwin = "/nix/store/0pbq6wzr2f1jgpn5212knyxpwmkjgjah-nix-2.18.1";
 }
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 7d0c5898e23df..2f9edba4f0c9c 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -102,22 +102,6 @@ sub cpuManufacturer {
     return $cpuinfo =~ /^vendor_id\s*:.* $id$/m;
 }
 
-
-# Determine CPU governor to use
-if (-e "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors") {
-    my $governors = read_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors");
-    # ondemand governor is not available on sandy bridge or later Intel CPUs
-    my @desired_governors = ("ondemand", "powersave");
-    my $e;
-
-    foreach $e (@desired_governors) {
-        if (index($governors, $e) != -1) {
-            last if (push @attrs, "powerManagement.cpuFreqGovernor = lib.mkDefault \"$e\";");
-        }
-    }
-}
-
-
 # Virtualization support?
 push @kernelModules, "kvm-intel" if hasCPUFeature "vmx";
 push @kernelModules, "kvm-amd" if hasCPUFeature "svm";
@@ -146,7 +130,7 @@ sub pciCheck {
     debug "\n";
 
     if (defined $module) {
-        # See the bottom of http://pciids.sourceforge.net/pci.ids for
+        # See the bottom of https://pciids.sourceforge.net/pci.ids for
         # device classes.
         if (# Mass-storage controller.  Definitely important.
             $class =~ /^0x01/ ||
@@ -273,6 +257,7 @@ foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
 
 # Add bcache module, if needed.
 my @bcacheDevices = glob("/dev/bcache*");
+@bcacheDevices = grep(!qr#dev/bcachefs.*#, @bcacheDevices);
 if (scalar @bcacheDevices > 0) {
     push @initrdAvailableKernelModules, "bcache";
 }
@@ -483,6 +468,19 @@ EOF
     # boot.tmp.useTmpfs option in configuration.nix (managed declaratively).
     next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");
 
+    # This should work for single and multi-device systems.
+    # still needs subvolume support
+    if ($fsType eq "bcachefs") {
+        my ($status, @info) = runCommand("bcachefs fs usage $rootDir$mountPoint");
+        my $UUID = $info[0];
+
+        if ($status == 0 && $UUID =~ /^Filesystem:[ \t\n]*([0-9a-z-]+)/) {
+            $stableDevPath = "UUID=$1";
+        } else {
+            print STDERR "warning: can't find bcachefs mount UUID falling back to device-path";
+        }
+    }
+
     # Emit the filesystem.
     $fileSystems .= <<EOF;
   fileSystems.\"$mountPoint\" =
diff --git a/nixos/modules/installer/tools/nixos-option/default.nix b/nixos/modules/installer/tools/nixos-option/default.nix
deleted file mode 100644
index 061460f38a3b6..0000000000000
--- a/nixos/modules/installer/tools/nixos-option/default.nix
+++ /dev/null
@@ -1 +0,0 @@
-{ pkgs, ... }: pkgs.nixos-option
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 6564b583464a2..a7d11370d445e 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -130,12 +130,12 @@ in
     '';
   };
 
-  config = lib.mkIf (config.nix.enable && !config.system.disableInstallerTools) {
+  config = lib.mkMerge [ (lib.mkIf (config.nix.enable && !config.system.disableInstallerTools) {
 
     system.nixos-generate-config.configuration = mkDefault ''
       # Edit this configuration file to define what should be installed on
-      # your system.  Help is available in the configuration.nix(5) man page
-      # and in the NixOS manual (accessible by running `nixos-help`).
+      # your system. Help is available in the configuration.nix(5) man page, on
+      # https://search.nixos.org/options and in the NixOS manual (`nixos-help`).
 
       { config, lib, pkgs, ... }:
 
@@ -163,15 +163,15 @@ in
         # console = {
         #   font = "Lat2-Terminus16";
         #   keyMap = "us";
-        #   useXkbConfig = true; # use xkbOptions in tty.
+        #   useXkbConfig = true; # use xkb.options in tty.
         # };
 
       $xserverConfig
 
       $desktopConfiguration
         # Configure keymap in X11
-        # services.xserver.layout = "us";
-        # services.xserver.xkbOptions = "eurosign:e,caps:escape";
+        # services.xserver.xkb.layout = "us";
+        # services.xserver.xkb.options = "eurosign:e,caps:escape";
 
         # Enable CUPS to print documents.
         # services.printing.enable = true;
@@ -224,12 +224,23 @@ in
         # accidentally delete configuration.nix.
         # system.copySystemConfiguration = true;
 
-        # This value determines the NixOS release from which the default
-        # settings for stateful data, like file locations and database versions
-        # on your system were taken. It's perfectly fine and recommended to leave
-        # this value at the release version of the first install of this system.
-        # Before changing this value read the documentation for this option
-        # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
+        # This option defines the first version of NixOS you have installed on this particular machine,
+        # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
+        #
+        # Most users should NEVER change this value after the initial install, for any reason,
+        # even if you've upgraded your system to a new NixOS release.
+        #
+        # This value does NOT affect the Nixpkgs version your packages and OS are pulled from,
+        # so changing it will NOT upgrade your system - see https://nixos.org/manual/nixos/stable/#sec-upgrading for how
+        # to actually do that.
+        #
+        # This value being lower than the current NixOS release does NOT mean your system is
+        # out of date, out of support, or vulnerable.
+        #
+        # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration,
+        # and migrated your data accordingly.
+        #
+        # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion .
         system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment?
 
       }
@@ -247,10 +258,13 @@ in
 
     documentation.man.man-db.skipPackages = [ nixos-version ];
 
+  })
+
+  # These may be used in auxiliary scripts (ie not part of toplevel), so they are defined unconditionally.
+  ({
     system.build = {
       inherit nixos-install nixos-generate-config nixos-option nixos-rebuild nixos-enter;
     };
-
-  };
+  })];
 
 }
diff --git a/nixos/modules/installer/virtualbox-demo.nix b/nixos/modules/installer/virtualbox-demo.nix
index 27a7651382b25..01931b2acfca4 100644
--- a/nixos/modules/installer/virtualbox-demo.nix
+++ b/nixos/modules/installer/virtualbox-demo.nix
@@ -21,7 +21,7 @@ with lib;
   services.xserver.videoDrivers = mkOverride 40 [ "virtualbox" "vmware" "cirrus" "vesa" "modesetting" ];
 
   powerManagement.enable = false;
-  system.stateVersion = mkDefault "18.03";
+  system.stateVersion = lib.mkDefault lib.trivial.release;
 
   installer.cloneConfigExtra = ''
   # Let demo build as a trusted user.
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 46462c5abd435..f3e698468e642 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -77,7 +77,11 @@ let
           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;
+          modules =
+            "[ "
+            + concatMapStringsSep " " (p: ''"${removePrefix "${modulesPath}/" (toString p)}"'') docModules.lazy
+            + " ]";
+          passAsFile = [ "modules" ];
         } ''
           export NIX_STORE_DIR=$TMPDIR/store
           export NIX_STATE_DIR=$TMPDIR/state
@@ -87,7 +91,7 @@ let
             --argstr libPath "$libPath" \
             --argstr pkgsLibPath "$pkgsLibPath" \
             --argstr nixosPath "$nixosPath" \
-            --arg modules "[ $modules ]" \
+            --arg modules "import $modulesPath" \
             --argstr stateVersion "${options.system.stateVersion.default}" \
             --argstr release "${config.system.nixos.release}" \
             $nixosPath/lib/eval-cacheable-options.nix > $out \
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index dc59ccb357d44..5af7284ac71af 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -69,7 +69,7 @@ in
       #dialout = 27; # unused
       polkituser = 28;
       #utmp = 29; # unused
-      # ddclient = 30; # software removed
+      # ddclient = 30; # converted to DynamicUser = true
       davfs2 = 31;
       disnix = 33;
       osgi = 34;
@@ -86,7 +86,7 @@ in
       #rtkit = 45; # dynamically allocated 2021-09-03
       dovecot2 = 46;
       dovenull2 = 47;
-      prayer = 49;
+      # prayer = 49; # dropped in 23.11
       mpd = 50;
       clamav = 51;
       #fprot = 52; # unused
@@ -288,7 +288,7 @@ in
       telegraf = 256;
       gitlab-runner = 257;
       postgrey = 258;
-      hound = 259;
+      # hound = 259; # unused, removed 2023-11-21
       leaps = 260;
       ipfs  = 261;
       # stanchion = 262; # unused, removed 2020-10-14
@@ -394,7 +394,7 @@ in
       dialout = 27;
       #polkituser = 28; # currently unused, polkitd doesn't need a group
       utmp = 29;
-      # ddclient = 30; # software removed
+      # ddclient = 30; # converted to DynamicUser = true
       davfs2 = 31;
       disnix = 33;
       osgi = 34;
@@ -411,7 +411,7 @@ in
       #rtkit = 45; # unused
       dovecot2 = 46;
       dovenull2 = 47;
-      prayer = 49;
+      # prayer = 49; # dropped in 23.11
       mpd = 50;
       clamav = 51;
       #fprot = 52; # unused
@@ -599,7 +599,7 @@ in
       #telegraf = 256; # unused
       gitlab-runner = 257;
       postgrey = 258;
-      hound = 259;
+      # hound = 259; # unused, removed 2023-11-21
       leaps = 260;
       ipfs = 261;
       # stanchion = 262; # unused, removed 2020-10-14
diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix
index acf441cda6288..0dd4bf3f16f34 100644
--- a/nixos/modules/misc/locate.nix
+++ b/nixos/modules/misc/locate.nix
@@ -4,14 +4,15 @@ with lib;
 
 let
   cfg = config.services.locate;
-  isMLocate = hasPrefix "mlocate" cfg.locate.name;
-  isPLocate = hasPrefix "plocate" cfg.locate.name;
-  isMorPLocate = (isMLocate || isPLocate);
-  isFindutils = hasPrefix "findutils" cfg.locate.name;
+  isMLocate = hasPrefix "mlocate" cfg.package.name;
+  isPLocate = hasPrefix "plocate" cfg.package.name;
+  isMorPLocate = isMLocate || isPLocate;
+  isFindutils = hasPrefix "findutils" cfg.package.name;
 in
 {
   imports = [
     (mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ])
+    (mkRenamedOptionModule [ "services" "locate" "locate" ] [ "services" "locate" "package" ])
     (mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths")
   ];
 
@@ -25,14 +26,8 @@ in
       '';
     };
 
-    locate = mkOption {
-      type = package;
-      default = pkgs.findutils.locate;
-      defaultText = literalExpression "pkgs.findutils";
-      example = literalExpression "pkgs.mlocate";
-      description = lib.mdDoc ''
-        The locate implementation to use
-      '';
+    package = mkPackageOption pkgs [ "findutils" "locate" ] {
+      example = "mlocate";
     };
 
     interval = mkOption {
@@ -216,25 +211,23 @@ in
           setgid = true;
           setuid = false;
         };
-        mlocate = (mkIf isMLocate {
+        mlocate = mkIf isMLocate {
           group = "mlocate";
-          source = "${cfg.locate}/bin/locate";
-        });
-        plocate = (mkIf isPLocate {
+          source = "${cfg.package}/bin/locate";
+        };
+        plocate = mkIf isPLocate {
           group = "plocate";
-          source = "${cfg.locate}/bin/plocate";
-        });
+          source = "${cfg.package}/bin/plocate";
+        };
       in
       mkIf isMorPLocate {
         locate = mkMerge [ common mlocate plocate ];
-        plocate = (mkIf isPLocate (mkMerge [ common plocate ]));
+        plocate = mkIf isPLocate (mkMerge [ common plocate ]);
       };
 
-    nixpkgs.config = { locate.dbfile = cfg.output; };
-
-    environment.systemPackages = [ cfg.locate ];
+    environment.systemPackages = [ cfg.package ];
 
-    environment.variables = mkIf (!isMorPLocate) { LOCATE_PATH = cfg.output; };
+    environment.variables.LOCATE_PATH = cfg.output;
 
     environment.etc = {
       # write /etc/updatedb.conf for manual calls to `updatedb`
@@ -270,13 +263,13 @@ in
             args = concatLists (map toFlags [ "pruneFS" "pruneNames" "prunePaths" ]);
           in
           ''
-            exec ${cfg.locate}/bin/updatedb \
+            exec ${cfg.package}/bin/updatedb \
               --output ${toString cfg.output} ${concatStringsSep " " args} \
               --prune-bind-mounts ${if cfg.pruneBindMounts then "yes" else "no"} \
               ${concatStringsSep " " cfg.extraFlags}
           ''
         else ''
-          exec ${cfg.locate}/bin/updatedb \
+          exec ${cfg.package}/bin/updatedb \
             ${optionalString (cfg.localuser != null && !isMorPLocate) "--localuser=${cfg.localuser}"} \
             --output=${toString cfg.output} ${concatStringsSep " " cfg.extraFlags}
         '';
diff --git a/nixos/modules/misc/mandoc.nix b/nixos/modules/misc/mandoc.nix
index 9bcef5b1a09bd..73646a60aabb2 100644
--- a/nixos/modules/misc/mandoc.nix
+++ b/nixos/modules/misc/mandoc.nix
@@ -5,25 +5,39 @@ let
 
   cfg = config.documentation.man.mandoc;
 
-in {
+  toMandocOutput = output: (
+    lib.mapAttrsToList
+      (
+        name: value:
+          if lib.isString value || lib.isPath value then "output ${name} ${value}"
+          else if lib.isInt value then "output ${name} ${builtins.toString value}"
+          else if lib.isBool value then lib.optionalString value "output ${name}"
+          else if value == null then ""
+          else throw "Unrecognized value type ${builtins.typeOf value} of key ${name} in mandoc output settings"
+      )
+      output
+  );
+in
+{
   meta.maintainers = [ lib.maintainers.sternenseemann ];
 
   options = {
     documentation.man.mandoc = {
-      enable = lib.mkEnableOption (lib.mdDoc "mandoc as the default man page viewer");
+      enable = lib.mkEnableOption "mandoc as the default man page viewer";
 
       manPath = lib.mkOption {
         type = with lib.types; listOf str;
         default = [ "share/man" ];
         example = lib.literalExpression "[ \"share/man\" \"share/man/fr\" ]";
-        description = lib.mdDoc ''
-          Change the manpath, i. e. the directories where
-          {manpage}`man(1)`
+        description = ''
+          Change the paths included in the MANPATH environment variable,
+          i. e. the directories where {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
           are a valid path from the target prefix (without including it).
-          The first value given takes priority.
+          The first value given takes priority. Note that this will not
+          add manpath directives to {manpage}`man.conf(5)`.
         '';
       };
 
@@ -31,11 +45,122 @@ in {
         type = lib.types.package;
         default = pkgs.mandoc;
         defaultText = lib.literalExpression "pkgs.mandoc";
-        description = lib.mdDoc ''
+        description = ''
           The `mandoc` derivation to use. Useful to override
           configuration options used for the package.
         '';
       };
+
+      settings = lib.mkOption {
+        description = "Configuration for {manpage}`man.conf(5)`";
+        default = { };
+        type = lib.types.submodule {
+          options = {
+            manpath = lib.mkOption {
+              type = with lib.types; listOf str;
+              default = [ ];
+              example = lib.literalExpression "[ \"/run/current-system/sw/share/man\" ]";
+              description = ''
+                Override the default search path for {manpage}`man(1)`,
+                {manpage}`apropos(1)`, and {manpage}`makewhatis(8)`. It can be
+                used multiple times to specify multiple paths, with the order
+                determining the manual page search order.
+                This is not recommended in favor of
+                {option}`documentation.man.mandoc.manPath`, but if it's needed to
+                specify the manpath in this way, set
+                {option}`documentation.man.mandoc.manPath` to an empty list (`[]`).
+              '';
+            };
+            output.fragment = lib.mkEnableOption ''
+              Omit the <!DOCTYPE> declaration and the <html>, <head>, and <body>
+              elements and only emit the subtree below the <body> element in HTML
+              output of {manpage}`mandoc(1)`. The style argument will be ignored.
+              This is useful when embedding manual content within existing documents.
+            '';
+            output.includes = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              example = lib.literalExpression "../src/%I.html";
+              description = ''
+                A string of relative path used as a template for the output path of
+                linked header files (usually via the In macro) in HTML output.
+                Instances of `%I` are replaced with the include filename. The
+                default is not to present a hyperlink.
+              '';
+            };
+            output.indent = lib.mkOption {
+              type = with lib.types; nullOr int;
+              default = null;
+              description = ''
+                Number of blank characters at the left margin for normal text,
+                default of `5` for {manpage}`mdoc(7)` and `7` for
+                {manpage}`man(7)`. Increasing this is not recommended; it may
+                result in degraded formatting, for example overfull lines or ugly
+                line breaks. When output is to a pager on a terminal that is less
+                than 66 columns wide, the default is reduced to three columns.
+              '';
+            };
+            output.man = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              example = lib.literalExpression "../html%S/%N.%S.html";
+              description = ''
+                A template for linked manuals (usually via the Xr macro) in HTML
+                output. Instances of ‘%N’ and ‘%S’ are replaced with the linked
+                manual's name and section, respectively. If no section is included,
+                section 1 is assumed. The default is not to present a hyperlink.
+                If two formats are given and a file %N.%S exists in the current
+                directory, the first format is used; otherwise, the second format is used.
+              '';
+            };
+            output.paper = lib.mkOption {
+              type = with lib.types; nullOr str;
+              default = null;
+              description = ''
+                This option is for generating PostScript and PDF output. The paper
+                size name may be one of `a3`, `a4`, `a5`, `legal`, or `letter`.
+                You may also manually specify dimensions as `NNxNN`, width by
+                height in millimetres. If an unknown value is encountered, letter
+                is used. Output pages default to letter sized and are rendered in
+                the Times font family, 11-point. Margins are calculated as 1/9 the
+                page length and width. Line-height is 1.4m.
+              '';
+            };
+            output.style = lib.mkOption {
+              type = with lib.types; nullOr path;
+              default = null;
+              description = ''
+                Path to the file used for an external style-sheet. This must be a
+                valid absolute or relative URI.
+              '';
+            };
+            output.toc = lib.mkEnableOption ''
+              In HTML output of {manpage}`mandoc(1)`, If an input file contains
+              at least two non-standard sections, print a table of contents near
+              the beginning of the output.
+            '';
+            output.width = lib.mkOption {
+              type = with lib.types; nullOr int;
+              default = null;
+              description = ''
+                The ASCII and UTF-8 output width, default is `78`. When output is a
+                pager on a terminal that is less than 79 columns wide, the
+                default is reduced to one less than the terminal width. In any case,
+                lines that are output in literal mode are never wrapped and may
+                exceed the output width.
+              '';
+            };
+          };
+        };
+      };
+
+      extraConfig = lib.mkOption {
+        type = lib.types.lines;
+        default = "";
+        description = ''
+          Extra configuration to write to {manpage}`man.conf(5)`.
+        '';
+      };
     };
   };
 
@@ -43,21 +168,29 @@ in {
     environment = {
       systemPackages = [ cfg.package ];
 
-      # tell mandoc about man pages
-      etc."man.conf".text = lib.concatMapStrings (path: ''
-        manpath /run/current-system/sw/${path}
-      '') cfg.manPath;
+      etc."man.conf".text = lib.concatStringsSep "\n" (
+        (map (path: "manpath ${path}") cfg.settings.manpath)
+        ++ (toMandocOutput cfg.settings.output)
+        ++ [ cfg.extraConfig ]
+      );
 
       # create mandoc.db for whatis(1), apropos(1) and man(1) -k
       # TODO(@sternenseemman): fix symlinked directories not getting indexed,
       # see: https://inbox.vuxu.org/mandoc-tech/20210906171231.GF83680@athene.usta.de/T/#e85f773c1781e3fef85562b2794f9cad7b2909a3c
       extraSetup = lib.mkIf config.documentation.man.generateCaches ''
-        ${makewhatis} -T utf8 ${
+        for man_path in ${
           lib.concatMapStringsSep " " (path:
             "$out/" + lib.escapeShellArg path
-          ) cfg.manPath
-        }
+            ) cfg.manPath} ${lib.concatMapStringsSep " " (path:
+            lib.escapeShellArg path) cfg.settings.manpath
+          }
+        do
+          [[ -d "$man_path" ]] && ${makewhatis} -T utf8 $man_path
+        done
       '';
+
+      # tell mandoc the paths containing man pages
+      profileRelativeSessionVariables."MANPATH" = map (path: if builtins.substring 0 1 path != "/" then "/${path}" else path) cfg.manPath;
     };
   };
 }
diff --git a/nixos/modules/misc/nixops-autoluks.nix b/nixos/modules/misc/nixops-autoluks.nix
index 221b34f3cc366..e6817633119d9 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 (lib.mdDoc "Enable the deprecated NixOps AutoLuks module");
+  options.nixops.enableDeprecatedAutoLuks = lib.mkEnableOption (lib.mdDoc "the deprecated NixOps AutoLuks module");
 
   config = {
     assertions = [
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index f9d8bccea2843..da321a9234493 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -176,22 +176,18 @@ in
         '';
       type = types.listOf overlayType;
       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 *original* Nixpkgs.
-        The first argument should be used for finding dependencies, and
-        the second should be used for overriding recipes.
-
-        If `nixpkgs.pkgs` is set, overlays specified here
-        will be applied after the overlays that were already present
-        in `nixpkgs.pkgs`.
+        List of overlays to apply to Nixpkgs.
+        This option allows modifying the Nixpkgs package set accessed through the `pkgs` module argument.
+
+        For details, see the [Overlays chapter in the Nixpkgs manual](https://nixos.org/manual/nixpkgs/stable/#chap-overlays).
+
+        If the {option}`nixpkgs.pkgs` option is set, overlays specified using `nixpkgs.overlays` will be applied after the overlays that were already included 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"; };
+      example = { system = "aarch64-linux"; };
       # 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;
@@ -209,7 +205,7 @@ in
     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"; };
+      example = { system = "x86_64-linux"; };
       # Make sure that the final value has all fields for sake of other modules
       # referring to this.
       apply = lib.systems.elaborate;
@@ -232,7 +228,7 @@ in
     localSystem = mkOption {
       type = types.attrs; # TODO utilize lib.systems.parsedPlatform
       default = { inherit (cfg) system; };
-      example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      example = { system = "aarch64-linux"; };
       # 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;
@@ -266,7 +262,7 @@ in
     crossSystem = mkOption {
       type = types.nullOr types.attrs; # TODO utilize lib.systems.parsedPlatform
       default = null;
-      example = { system = "aarch64-linux"; config = "aarch64-unknown-linux-gnu"; };
+      example = { system = "aarch64-linux"; };
       description = lib.mdDoc ''
         Systems with a recently generated `hardware-configuration.nix`
         may instead specify *only* {option}`nixpkgs.buildPlatform`,
@@ -383,6 +379,16 @@ in
           the legacy definitions.
         '';
       }
+      {
+        assertion = opt.pkgs.isDefined -> cfg.config == {};
+        message = ''
+          Your system configures nixpkgs with an externally created instance.
+          `nixpkgs.config` options should be passed when creating the instance instead.
+
+          Current value:
+          ${lib.generators.toPretty { multiline = true; } opt.config}
+        '';
+      }
     ];
   };
 
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index 0a66eafe933ea..c929c3b37285b 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -28,6 +28,8 @@ let
     DOCUMENTATION_URL = lib.optionalString (cfg.distroId == "nixos") "https://nixos.org/learn.html";
     SUPPORT_URL = lib.optionalString (cfg.distroId == "nixos") "https://nixos.org/community.html";
     BUG_REPORT_URL = lib.optionalString (cfg.distroId == "nixos") "https://github.com/NixOS/nixpkgs/issues";
+    IMAGE_ID = lib.optionalString (config.system.image.id != null) config.system.image.id;
+    IMAGE_VERSION = lib.optionalString (config.system.image.version != null) config.system.image.version;
   } // lib.optionalAttrs (cfg.variant_id != null) {
     VARIANT_ID = cfg.variant_id;
   };
@@ -110,6 +112,38 @@ in
       example = "installer";
     };
 
+    image = {
+
+      id = lib.mkOption {
+        type = types.nullOr (types.strMatching "^[a-z0-9._-]+$");
+        default = null;
+        description = lib.mdDoc ''
+          Image identifier.
+
+          This corresponds to the IMAGE_ID field in os-release. See the
+          upstream docs for more details on valid characters for this field:
+          https://www.freedesktop.org/software/systemd/man/latest/os-release.html#IMAGE_ID=
+
+          You would only want to set this option if you're build NixOS appliance images.
+        '';
+      };
+
+      version = lib.mkOption {
+        type = types.nullOr (types.strMatching "^[a-z0-9._-]+$");
+        default = null;
+        description = lib.mdDoc ''
+          Image version.
+
+          This corresponds to the IMAGE_VERSION field in os-release. See the
+          upstream docs for more details on valid characters for this field:
+          https://www.freedesktop.org/software/systemd/man/latest/os-release.html#IMAGE_VERSION=
+
+          You would only want to set this option if you're build NixOS appliance images.
+        '';
+      };
+
+    };
+
     stateVersion = mkOption {
       type = types.str;
       # TODO Remove this and drop the default of the option so people are forced to set it.
@@ -121,22 +155,32 @@ in
       default = cfg.release;
       defaultText = literalExpression "config.${opt.release}";
       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
-        changes, the new version will probably be unable to read your
-        existing databases. To prevent such breakage, you should set the
-        value of this option to the NixOS release with which you want
-        to be compatible. The effect is that NixOS will use
-        defaults corresponding to the specified release (such as using
-        an older version of PostgreSQL).
-        It’s perfectly fine and recommended to leave this value at the
-        release version of the first install of this system.
-        Changing this option will not upgrade your system. In fact it
-        is meant to stay constant exactly when you upgrade your system.
-        You should only bump this option, if you are sure that you can
-        or have migrated all state on your system which is affected
-        by this option.
+        This option defines the first version of NixOS you have installed on this particular machine,
+        and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions.
+
+        For example, if NixOS version XX.YY ships with AwesomeDB version N by default, and is then
+        upgraded to version XX.YY+1, which ships AwesomeDB version N+1, the existing databases
+        may no longer be compatible, causing applications to fail, or even leading to data loss.
+
+        The `stateVersion` mechanism avoids this situation by making the default version of such packages
+        conditional on the first version of NixOS you've installed (encoded in `stateVersion`), instead of
+        simply always using the latest one.
+
+        Note that this generally only affects applications that can't upgrade their data automatically -
+        applications and services supporting automatic migrations will remain on latest versions when
+        you upgrade.
+
+        Most users should **never** change this value after the initial install, for any reason,
+        even if you've upgraded your system to a new NixOS release.
+
+        This value does **not** affect the Nixpkgs version your packages and OS are pulled from,
+        so changing it will **not** upgrade your system.
+
+        This value being lower than the current NixOS release does **not** mean your system is
+        out of date, out of support, or vulnerable.
+
+        Do **not** change this value unless you have manually inspected all the changes it would
+        make to your configuration, and migrated your data accordingly.
       '';
     };
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index abe0847ade21e..2552ca6fa0f54 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -2,16 +2,16 @@
   ./config/appstream.nix
   ./config/console.nix
   ./config/debug-info.nix
+  ./config/fanout.nix
   ./config/fonts/fontconfig.nix
   ./config/fonts/fontdir.nix
   ./config/fonts/ghostscript.nix
   ./config/fonts/packages.nix
-  ./config/gnu.nix
   ./config/gtk/gtk-icon-cache.nix
   ./config/i18n.nix
   ./config/iproute2.nix
-  ./config/krb5/default.nix
   ./config/ldap.nix
+  ./config/ldso.nix
   ./config/locale.nix
   ./config/malloc.nix
   ./config/mysql.nix
@@ -28,6 +28,7 @@
   ./config/resolvconf.nix
   ./config/shells-environment.nix
   ./config/stevenblack.nix
+  ./config/stub-ld.nix
   ./config/swap.nix
   ./config/sysctl.nix
   ./config/system-environment.nix
@@ -55,14 +56,18 @@
   ./hardware/cpu/amd-sev.nix
   ./hardware/cpu/intel-microcode.nix
   ./hardware/cpu/intel-sgx.nix
+  ./hardware/cpu/x86-msr.nix
+  ./hardware/decklink.nix
   ./hardware/device-tree.nix
   ./hardware/digitalbitbox.nix
   ./hardware/flipperzero.nix
   ./hardware/flirc.nix
   ./hardware/gkraken.nix
+  ./hardware/glasgow.nix
   ./hardware/gpgsmartcards.nix
   ./hardware/hackrf.nix
   ./hardware/i2c.nix
+  ./hardware/infiniband.nix
   ./hardware/keyboard/qmk.nix
   ./hardware/keyboard/teck.nix
   ./hardware/keyboard/uhk.nix
@@ -163,6 +168,8 @@
   ./programs/direnv.nix
   ./programs/dmrconfig.nix
   ./programs/droidcam.nix
+  ./programs/dublin-traceroute.nix
+  ./programs/ecryptfs.nix
   ./programs/environment.nix
   ./programs/evince.nix
   ./programs/extra-container.nix
@@ -228,6 +235,7 @@
   ./programs/pantheon-tweaks.nix
   ./programs/partition-manager.nix
   ./programs/plotinus.nix
+  ./programs/projecteur.nix
   ./programs/proxychains.nix
   ./programs/qdmr.nix
   ./programs/qt5ct.nix
@@ -261,12 +269,16 @@
   ./programs/udevil.nix
   ./programs/usbtop.nix
   ./programs/vim.nix
+  ./programs/virt-manager.nix
   ./programs/wavemon.nix
+  ./programs/wayland/cardboard.nix
+  ./programs/wayland/labwc.nix
   ./programs/wayland/river.nix
   ./programs/wayland/sway.nix
   ./programs/wayland/waybar.nix
   ./programs/wayland/wayfire.nix
   ./programs/weylus.nix
+  ./programs/winbox.nix
   ./programs/wireshark.nix
   ./programs/xastir.nix
   ./programs/wshowkeys.nix
@@ -276,6 +288,8 @@
   ./programs/xss-lock.nix
   ./programs/xwayland.nix
   ./programs/yabar.nix
+  ./programs/yazi.nix
+  ./programs/yubikey-touch-detector.nix
   ./programs/zmap.nix
   ./programs/zsh/oh-my-zsh.nix
   ./programs/zsh/zsh-autoenv.nix
@@ -294,6 +308,7 @@
   ./security/duosec.nix
   ./security/google_oslogin.nix
   ./security/ipa.nix
+  ./security/krb5
   ./security/lock-kernel-modules.nix
   ./security/misc.nix
   ./security/oath.nix
@@ -305,6 +320,7 @@
   ./security/rngd.nix
   ./security/rtkit.nix
   ./security/sudo.nix
+  ./security/sudo-rs.nix
   ./security/systemd-confinement.nix
   ./security/tpm2.nix
   ./security/wrappers/default.nix
@@ -317,6 +333,7 @@
   ./services/amqp/rabbitmq.nix
   ./services/audio/alsa.nix
   ./services/audio/botamusique.nix
+  ./services/audio/castopod.nix
   ./services/audio/gmediarender.nix
   ./services/audio/gonic.nix
   ./services/audio/goxlr-utility.nix
@@ -328,6 +345,7 @@
   ./services/audio/mopidy.nix
   ./services/audio/mpd.nix
   ./services/audio/mpdscribble.nix
+  ./services/audio/mympd.nix
   ./services/audio/navidrome.nix
   ./services/audio/networkaudiod.nix
   ./services/audio/roon-bridge.nix
@@ -338,6 +356,7 @@
   ./services/audio/squeezelite.nix
   ./services/audio/tts.nix
   ./services/audio/wyoming/faster-whisper.nix
+  ./services/audio/wyoming/openwakeword.nix
   ./services/audio/wyoming/piper.nix
   ./services/audio/ympd.nix
   ./services/backup/automysqlbackup.nix
@@ -350,6 +369,7 @@
   ./services/backup/mysql-backup.nix
   ./services/backup/postgresql-backup.nix
   ./services/backup/postgresql-wal-receiver.nix
+  ./services/backup/snapraid.nix
   ./services/backup/restic-rest-server.nix
   ./services/backup/restic.nix
   ./services/backup/rsnapshot.nix
@@ -393,7 +413,6 @@
   ./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
@@ -408,6 +427,7 @@
   ./services/databases/couchdb.nix
   ./services/databases/dgraph.nix
   ./services/databases/dragonflydb.nix
+  ./services/databases/ferretdb.nix
   ./services/databases/firebird.nix
   ./services/databases/foundationdb.nix
   ./services/databases/hbase-standalone.nix
@@ -428,6 +448,7 @@
   ./services/databases/surrealdb.nix
   ./services/databases/victoriametrics.nix
   ./services/desktops/accountsservice.nix
+  ./services/desktops/ayatana-indicators.nix
   ./services/desktops/bamf.nix
   ./services/desktops/blueman.nix
   ./services/desktops/cpupower-gui.nix
@@ -461,11 +482,13 @@
   ./services/desktops/pipewire/pipewire.nix
   ./services/desktops/pipewire/wireplumber.nix
   ./services/desktops/profile-sync-daemon.nix
+  ./services/desktops/seatd.nix
   ./services/desktops/system-config-printer.nix
   ./services/desktops/system76-scheduler.nix
   ./services/desktops/telepathy.nix
   ./services/desktops/tumbler.nix
   ./services/desktops/zeitgeist.nix
+  ./services/development/athens.nix
   ./services/development/blackfire.nix
   ./services/development/bloop.nix
   ./services/development/distccd.nix
@@ -473,7 +496,9 @@
   ./services/development/hoogle.nix
   ./services/development/jupyter/default.nix
   ./services/development/jupyterhub/default.nix
+  ./services/development/livebook.nix
   ./services/development/lorri.nix
+  ./services/development/nixseparatedebuginfod.nix
   ./services/development/rstudio-server/default.nix
   ./services/development/zammad.nix
   ./services/display-managers/greetd.nix
@@ -481,7 +506,7 @@
   ./services/editors/haste.nix
   ./services/editors/infinoted.nix
   ./services/finance/odoo.nix
-  ./services/games/asf.nix
+  ./services/games/archisteamfarm.nix
   ./services/games/crossfire-server.nix
   ./services/games/deliantra-server.nix
   ./services/games/factorio.nix
@@ -493,11 +518,13 @@
   ./services/games/quake3-server.nix
   ./services/games/teeworlds.nix
   ./services/games/terraria.nix
+  ./services/games/xonotic.nix
   ./services/hardware/acpid.nix
   ./services/hardware/actkbd.nix
   ./services/hardware/argonone.nix
   ./services/hardware/asusd.nix
   ./services/hardware/auto-cpufreq.nix
+  ./services/hardware/auto-epp.nix
   ./services/hardware/bluetooth.nix
   ./services/hardware/bolt.nix
   ./services/hardware/brltty.nix
@@ -508,6 +535,7 @@
   ./services/hardware/hddfancontrol.nix
   ./services/hardware/illum.nix
   ./services/hardware/interception-tools.nix
+  ./services/hardware/iptsd.nix
   ./services/hardware/irqbalance.nix
   ./services/hardware/joycond.nix
   ./services/hardware/kanata.nix
@@ -533,6 +561,7 @@
   ./services/hardware/tlp.nix
   ./services/hardware/trezord.nix
   ./services/hardware/triggerhappy.nix
+  ./services/hardware/tuxedo-rs.nix
   ./services/hardware/udev.nix
   ./services/hardware/udisks2.nix
   ./services/hardware/undervolt.nix
@@ -545,7 +574,9 @@
   ./services/home-automation/esphome.nix
   ./services/home-automation/evcc.nix
   ./services/home-automation/home-assistant.nix
+  ./services/home-automation/homeassistant-satellite.nix
   ./services/home-automation/zigbee2mqtt.nix
+  ./services/home-automation/zwave-js.nix
   ./services/logging/SystemdJournal2Gelf.nix
   ./services/logging/awstats.nix
   ./services/logging/filebeat.nix
@@ -591,6 +622,7 @@
   ./services/mail/public-inbox.nix
   ./services/mail/roundcube.nix
   ./services/mail/rspamd.nix
+  ./services/mail/rspamd-trainer.nix
   ./services/mail/rss2email.nix
   ./services/mail/schleuder.nix
   ./services/mail/spamassassin.nix
@@ -601,6 +633,7 @@
   ./services/matrix/appservice-irc.nix
   ./services/matrix/conduit.nix
   ./services/matrix/dendrite.nix
+  ./services/matrix/maubot.nix
   ./services/matrix/mautrix-facebook.nix
   ./services/matrix/mautrix-telegram.nix
   ./services/matrix/mautrix-whatsapp.nix
@@ -610,8 +643,10 @@
   ./services/matrix/matrix-sliding-sync.nix
   ./services/matrix/synapse.nix
   ./services/misc/airsonic.nix
+  ./services/misc/amazon-ssm-agent.nix
   ./services/misc/ananicy.nix
   ./services/misc/ankisyncd.nix
+  ./services/misc/anki-sync-server.nix
   ./services/misc/apache-kafka.nix
   ./services/misc/atuin.nix
   ./services/misc/autofs.nix
@@ -644,6 +679,7 @@
   ./services/misc/etesync-dav.nix
   ./services/misc/evdevremapkeys.nix
   ./services/misc/felix.nix
+  ./services/misc/forgejo.nix
   ./services/misc/freeswitch.nix
   ./services/misc/fstrim.nix
   ./services/misc/gammu-smsd.nix
@@ -656,6 +692,7 @@
   ./services/misc/gollum.nix
   ./services/misc/gpsd.nix
   ./services/misc/greenclip.nix
+  ./services/misc/guix
   ./services/misc/headphones.nix
   ./services/misc/heisenbridge.nix
   ./services/misc/homepage-dashboard.nix
@@ -688,6 +725,7 @@
   ./services/misc/nzbget.nix
   ./services/misc/nzbhydra2.nix
   ./services/misc/octoprint.nix
+  ./services/misc/ollama.nix
   ./services/misc/ombi.nix
   ./services/misc/osrm.nix
   ./services/misc/owncast.nix
@@ -701,6 +739,7 @@
   ./services/misc/podgrab.nix
   ./services/misc/polaris.nix
   ./services/misc/portunus.nix
+  ./services/misc/preload.nix
   ./services/misc/prowlarr.nix
   ./services/misc/pufferpanel.nix
   ./services/misc/pykms.nix
@@ -710,6 +749,7 @@
   ./services/misc/ripple-data-api.nix
   ./services/misc/rippled.nix
   ./services/misc/rmfakecloud.nix
+  ./services/misc/rkvm.nix
   ./services/misc/rshim.nix
   ./services/misc/safeeyes.nix
   ./services/misc/sdrplay.nix
@@ -718,11 +758,12 @@
   ./services/misc/signald.nix
   ./services/misc/siproxd.nix
   ./services/misc/snapper.nix
+  ./services/misc/soft-serve.nix
   ./services/misc/sonarr.nix
   ./services/misc/sourcehut
+  ./services/misc/spice-autorandr.nix
   ./services/misc/spice-vdagentd.nix
   ./services/misc/spice-webdavd.nix
-  ./services/misc/ssm-agent.nix
   ./services/misc/sssd.nix
   ./services/misc/subsonic.nix
   ./services/misc/sundtek.nix
@@ -734,6 +775,7 @@
   ./services/misc/tautulli.nix
   ./services/misc/tiddlywiki.nix
   ./services/misc/tp-auto-kbbl.nix
+  ./services/misc/tuxclocker.nix
   ./services/misc/tzupdate.nix
   ./services/misc/uhub.nix
   ./services/misc/weechat.nix
@@ -747,12 +789,14 @@
   ./services/monitoring/below.nix
   ./services/monitoring/bosun.nix
   ./services/monitoring/cadvisor.nix
+  ./services/monitoring/certspotter.nix
   ./services/monitoring/cockpit.nix
   ./services/monitoring/collectd.nix
   ./services/monitoring/das_watchdog.nix
   ./services/monitoring/datadog-agent.nix
   ./services/monitoring/do-agent.nix
   ./services/monitoring/fusion-inventory.nix
+  ./services/monitoring/goss.nix
   ./services/monitoring/grafana-agent.nix
   ./services/monitoring/grafana-image-renderer.nix
   ./services/monitoring/grafana-reporter.nix
@@ -764,6 +808,7 @@
   ./services/monitoring/kapacitor.nix
   ./services/monitoring/karma.nix
   ./services/monitoring/kthxbye.nix
+  ./services/monitoring/librenms.nix
   ./services/monitoring/loki.nix
   ./services/monitoring/longview.nix
   ./services/monitoring/mackerel-agent.nix
@@ -773,6 +818,7 @@
   ./services/monitoring/munin.nix
   ./services/monitoring/nagios.nix
   ./services/monitoring/netdata.nix
+  ./services/monitoring/ocsinventory-agent.nix
   ./services/monitoring/opentelemetry-collector.nix
   ./services/monitoring/osquery.nix
   ./services/monitoring/parsedmarc.nix
@@ -788,6 +834,7 @@
   ./services/monitoring/riemann.nix
   ./services/monitoring/scollector.nix
   ./services/monitoring/smartd.nix
+  ./services/monitoring/snmpd.nix
   ./services/monitoring/statsd.nix
   ./services/monitoring/sysstat.nix
   ./services/monitoring/teamviewer.nix
@@ -802,6 +849,7 @@
   ./services/monitoring/vmagent.nix
   ./services/monitoring/vmalert.nix
   ./services/monitoring/vnstat.nix
+  ./services/monitoring/watchdogd.nix
   ./services/monitoring/zabbix-agent.nix
   ./services/monitoring/zabbix-proxy.nix
   ./services/monitoring/zabbix-server.nix
@@ -854,6 +902,7 @@
   ./services/networking/bitlbee.nix
   ./services/networking/blockbook-frontend.nix
   ./services/networking/blocky.nix
+  ./services/networking/centrifugo.nix
   ./services/networking/cgit.nix
   ./services/networking/charybdis.nix
   ./services/networking/chisel-server.nix
@@ -870,6 +919,8 @@
   ./services/networking/croc.nix
   ./services/networking/dae.nix
   ./services/networking/dante.nix
+  ./services/networking/deconz.nix
+  ./services/networking/ddclient.nix
   ./services/networking/dhcpcd.nix
   ./services/networking/dnscache.nix
   ./services/networking/dnscrypt-proxy2.nix
@@ -885,6 +936,7 @@
   ./services/networking/eternal-terminal.nix
   ./services/networking/expressvpn.nix
   ./services/networking/fakeroute.nix
+  ./services/networking/fastnetmon-advanced.nix
   ./services/networking/ferm.nix
   ./services/networking/firefox-syncserver.nix
   ./services/networking/fireqos.nix
@@ -894,12 +946,14 @@
   ./services/networking/flannel.nix
   ./services/networking/freenet.nix
   ./services/networking/freeradius.nix
+  ./services/networking/frp.nix
   ./services/networking/frr.nix
   ./services/networking/gateone.nix
   ./services/networking/gdomap.nix
   ./services/networking/ghostunnel.nix
   ./services/networking/git-daemon.nix
   ./services/networking/globalprotect-vpn.nix
+  ./services/networking/gns3-server.nix
   ./services/networking/gnunet.nix
   ./services/networking/go-autoconfig.nix
   ./services/networking/go-neb.nix
@@ -930,6 +984,7 @@
   ./services/networking/iwd.nix
   ./services/networking/jibri/default.nix
   ./services/networking/jicofo.nix
+  ./services/networking/jigasi.nix
   ./services/networking/jitsi-videobridge.nix
   ./services/networking/jool.nix
   ./services/networking/kea.nix
@@ -973,6 +1028,7 @@
   ./services/networking/ndppd.nix
   ./services/networking/nebula.nix
   ./services/networking/netbird.nix
+  ./services/networking/netclient.nix
   ./services/networking/networkd-dispatcher.nix
   ./services/networking/networkmanager.nix
   ./services/networking/nextdns.nix
@@ -989,6 +1045,7 @@
   ./services/networking/ntopng.nix
   ./services/networking/ntp/chrony.nix
   ./services/networking/ntp/ntpd.nix
+  ./services/networking/ntp/ntpd-rs.nix
   ./services/networking/ntp/openntpd.nix
   ./services/networking/nullidentdmod.nix
   ./services/networking/nylon.nix
@@ -1010,7 +1067,6 @@
   ./services/networking/powerdns.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
@@ -1023,6 +1079,7 @@
   ./services/networking/redsocks.nix
   ./services/networking/resilio.nix
   ./services/networking/robustirc-bridge.nix
+  ./services/networking/rosenpass.nix
   ./services/networking/routedns.nix
   ./services/networking/rpcbind.nix
   ./services/networking/rxe.nix
@@ -1064,13 +1121,13 @@
   ./services/networking/tayga.nix
   ./services/networking/tcpcrypt.nix
   ./services/networking/teamspeak3.nix
-  ./services/networking/tedicross.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/tinyproxy.nix
   ./services/networking/tmate-ssh-server.nix
   ./services/networking/tox-bootstrapd.nix
   ./services/networking/tox-node.nix
@@ -1116,13 +1173,14 @@
   ./services/search/elasticsearch-curator.nix
   ./services/search/elasticsearch.nix
   ./services/search/hound.nix
-  ./services/search/kibana.nix
   ./services/search/meilisearch.nix
   ./services/search/opensearch.nix
   ./services/search/qdrant.nix
+  ./services/search/sonic-server.nix
   ./services/search/typesense.nix
   ./services/security/aesmd.nix
   ./services/security/authelia.nix
+  ./services/security/bitwarden-directory-connector-cli.nix
   ./services/security/certmgr.nix
   ./services/security/cfssl.nix
   ./services/security/clamav.nix
@@ -1137,6 +1195,7 @@
   ./services/security/hologram-agent.nix
   ./services/security/hologram-server.nix
   ./services/security/infnoise.nix
+  ./services/security/jitterentropy-rngd.nix
   ./services/security/kanidm.nix
   ./services/security/munge.nix
   ./services/security/nginx-sso.nix
@@ -1145,12 +1204,12 @@
   ./services/security/opensnitch.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
   ./services/security/sslmate-agent.nix
   ./services/security/step-ca.nix
+  ./services/security/tang.nix
   ./services/security/tor.nix
   ./services/security/torify.nix
   ./services/security/torsocks.nix
@@ -1182,6 +1241,7 @@
   ./services/torrent/peerflix.nix
   ./services/torrent/rtorrent.nix
   ./services/torrent/transmission.nix
+  ./services/torrent/torrentstream.nix
   ./services/tracing/tempo.nix
   ./services/ttys/getty.nix
   ./services/ttys/gpm.nix
@@ -1201,7 +1261,9 @@
   ./services/web-apps/atlassian/confluence.nix
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
+  ./services/web-apps/audiobookshelf.nix
   ./services/web-apps/bookstack.nix
+  ./services/web-apps/c2fmzq-server.nix
   ./services/web-apps/calibre-web.nix
   ./services/web-apps/coder.nix
   ./services/web-apps/changedetection-io.nix
@@ -1241,6 +1303,7 @@
   ./services/web-apps/kavita.nix
   ./services/web-apps/keycloak.nix
   ./services/web-apps/komga.nix
+  ./services/web-apps/lanraragi.nix
   ./services/web-apps/lemmy.nix
   ./services/web-apps/limesurvey.nix
   ./services/web-apps/mainsail.nix
@@ -1248,6 +1311,8 @@
   ./services/web-apps/matomo.nix
   ./services/web-apps/mattermost.nix
   ./services/web-apps/mediawiki.nix
+  ./services/web-apps/meme-bingo-web.nix
+  ./services/web-apps/microbin.nix
   ./services/web-apps/miniflux.nix
   ./services/web-apps/monica.nix
   ./services/web-apps/moodle.nix
@@ -1259,6 +1324,7 @@
   ./services/web-apps/node-red.nix
   ./services/web-apps/onlyoffice.nix
   ./services/web-apps/openvscode-server.nix
+  ./services/web-apps/mobilizon.nix
   ./services/web-apps/openwebrx.nix
   ./services/web-apps/outline.nix
   ./services/web-apps/peering-manager.nix
@@ -1272,7 +1338,9 @@
   ./services/web-apps/powerdns-admin.nix
   ./services/web-apps/prosody-filer.nix
   ./services/web-apps/restya-board.nix
+  ./services/web-apps/rimgo.nix
   ./services/web-apps/sftpgo.nix
+  ./services/web-apps/suwayomi-server.nix
   ./services/web-apps/rss-bridge.nix
   ./services/web-apps/selfoss.nix
   ./services/web-apps/shiori.nix
@@ -1284,10 +1352,12 @@
   ./services/web-apps/vikunja.nix
   ./services/web-apps/whitebophir.nix
   ./services/web-apps/wiki-js.nix
+  ./services/web-apps/windmill.nix
   ./services/web-apps/wordpress.nix
   ./services/web-apps/writefreely.nix
   ./services/web-apps/youtrack.nix
   ./services/web-apps/zabbix.nix
+  ./services/web-apps/zitadel.nix
   ./services/web-servers/agate.nix
   ./services/web-servers/apache-httpd/default.nix
   ./services/web-servers/caddy/default.nix
@@ -1308,6 +1378,7 @@
   ./services/web-servers/molly-brown.nix
   ./services/web-servers/nginx/default.nix
   ./services/web-servers/nginx/gitweb.nix
+  ./services/web-servers/nginx/tailscale-auth.nix
   ./services/web-servers/phpfpm/default.nix
   ./services/web-servers/pomerium.nix
   ./services/web-servers/rustus.nix
@@ -1365,13 +1436,16 @@
   ./services/x11/xautolock.nix
   ./services/x11/xbanish.nix
   ./services/x11/xfs.nix
+  ./services/x11/xscreensaver.nix
   ./services/x11/xserver.nix
   ./system/activation/activatable-system.nix
   ./system/activation/activation-script.nix
   ./system/activation/specialisation.nix
+  ./system/activation/switchable-system.nix
   ./system/activation/bootspec.nix
   ./system/activation/top-level.nix
   ./system/boot/binfmt.nix
+  ./system/boot/clevis.nix
   ./system/boot/emergency-mode.nix
   ./system/boot/grow-partition.nix
   ./system/boot/initrd-network.nix
@@ -1394,6 +1468,8 @@
   ./system/boot/stratisroot.nix
   ./system/boot/modprobe.nix
   ./system/boot/networkd.nix
+  ./system/boot/uki.nix
+  ./system/boot/unl0kr.nix
   ./system/boot/plymouth.nix
   ./system/boot/resolved.nix
   ./system/boot/shutdown.nix
@@ -1404,12 +1480,16 @@
   ./system/boot/systemd/initrd-secrets.nix
   ./system/boot/systemd/initrd.nix
   ./system/boot/systemd/journald.nix
+  ./system/boot/systemd/journald-gateway.nix
+  ./system/boot/systemd/journald-remote.nix
+  ./system/boot/systemd/journald-upload.nix
   ./system/boot/systemd/logind.nix
   ./system/boot/systemd/nspawn.nix
   ./system/boot/systemd/oomd.nix
   ./system/boot/systemd/repart.nix
   ./system/boot/systemd/shutdown.nix
   ./system/boot/systemd/sysupdate.nix
+  ./system/boot/systemd/sysusers.nix
   ./system/boot/systemd/tmpfiles.nix
   ./system/boot/systemd/user.nix
   ./system/boot/systemd/userdbd.nix
@@ -1449,7 +1529,6 @@
   ./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
@@ -1466,6 +1545,7 @@
   ./virtualisation/docker.nix
   ./virtualisation/ecs-agent.nix
   ./virtualisation/hyperv-guest.nix
+  ./virtualisation/incus.nix
   ./virtualisation/kvmgt.nix
   ./virtualisation/libvirtd.nix
   ./virtualisation/lxc.nix
@@ -1476,6 +1556,7 @@
   ./virtualisation/nixos-containers.nix
   ./virtualisation/oci-containers.nix
   ./virtualisation/openstack-options.nix
+  ./virtualisation/oci-options.nix
   ./virtualisation/openvswitch.nix
   ./virtualisation/parallels-guest.nix
   ./virtualisation/podman/default.nix
@@ -1489,5 +1570,10 @@
   ./virtualisation/waydroid.nix
   ./virtualisation/xe-guest-utilities.nix
   ./virtualisation/xen-dom0.nix
-  { documentation.nixos.extraModules = [ ./virtualisation/qemu-vm.nix ]; }
+  {
+    documentation.nixos.extraModules = [
+      ./virtualisation/qemu-vm.nix
+      ./image/repart.nix
+    ];
+  }
 ]
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
index 856ee480fc0b6..74dc2cb1b9aa4 100644
--- a/nixos/modules/profiles/hardened.nix
+++ b/nixos/modules/profiles/hardened.nix
@@ -79,10 +79,6 @@ with lib;
     "ufs"
   ];
 
-  # Restrict ptrace() usage to processes with a pre-defined relationship
-  # (e.g., parent/child)
-  boot.kernel.sysctl."kernel.yama.ptrace_scope" = mkOverride 500 1;
-
   # Hide kptrs even for processes with CAP_SYSLOG
   boot.kernel.sysctl."kernel.kptr_restrict" = mkOverride 500 2;
 
diff --git a/nixos/modules/profiles/image-based-appliance.nix b/nixos/modules/profiles/image-based-appliance.nix
new file mode 100644
index 0000000000000..7e8b6f696d54f
--- /dev/null
+++ b/nixos/modules/profiles/image-based-appliance.nix
@@ -0,0 +1,26 @@
+# This profile sets up a sytem for image based appliance usage. An appliance is
+# installed as an image, cannot be re-built, has no Nix available, and is
+# generally not meant for interactive use. Updates to such an appliance are
+# handled by updating whole partition images via a tool like systemd-sysupdate.
+
+{ lib, modulesPath, ... }:
+
+{
+
+  # Appliances are always "minimal".
+  imports = [
+    "${modulesPath}/profiles/minimal.nix"
+  ];
+
+  # The system cannot be rebuilt.
+  nix.enable = false;
+  system.switch.enable = false;
+
+  # The system is static.
+  users.mutableUsers = false;
+
+  # The system avoids interpreters as much as possible to reduce its attack
+  # surface.
+  boot.initrd.systemd.enable = lib.mkDefault true;
+  networking.useNetworkd = lib.mkDefault true;
+}
diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix
index 19e7eb32e833f..58f07b050b5c4 100644
--- a/nixos/modules/profiles/installation-device.nix
+++ b/nixos/modules/profiles/installation-device.nix
@@ -102,11 +102,11 @@ with lib;
         jq # for closureInfo
         # For boot.initrd.systemd
         makeInitrdNGTool
-        systemdStage1
-        systemdStage1Network
       ];
 
     boot.swraid.enable = true;
+    # remove warning about unset mail
+    boot.swraid.mdadmConf = "PROGRAM ${pkgs.coreutils}/bin/true";
 
     # Show all debug messages from the kernel but don't log refused packets
     # because we have the firewall enabled. This makes installs from the
diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix
index cc01b16960ce4..6c2602881d6b5 100644
--- a/nixos/modules/profiles/macos-builder.nix
+++ b/nixos/modules/profiles/macos-builder.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, options, ... }:
 
 let
   keysDirectory = "/var/keys";
@@ -103,6 +103,19 @@ in
     # server that QEMU provides (normally 10.0.2.3)
     networking.nameservers = [ "8.8.8.8" ];
 
+    # The linux builder is a lightweight VM for remote building; not evaluation.
+    nix.channel.enable = false;
+    # remote builder uses `nix-daemon` (ssh-ng:) or `nix-store --serve` (ssh:)
+    # --force: do not complain when missing
+    # TODO: install a store-only nix
+    #       https://github.com/NixOS/rfcs/blob/master/rfcs/0134-nix-store-layer.md#detailed-design
+    environment.extraSetup = ''
+      rm --force $out/bin/{nix-instantiate,nix-build,nix-shell,nix-prefetch*,nix}
+    '';
+    # Deployment is by image.
+    # TODO system.switch.enable = false;?
+    system.disableInstallerTools = true;
+
     nix.settings = {
       auto-optimise-store = true;
 
@@ -163,9 +176,15 @@ in
 
       in
       script.overrideAttrs (old: {
+        pos = __curPos; # sets meta.position to point here; see script binding above for package definition
         meta = (old.meta or { }) // {
           platforms = lib.platforms.darwin;
         };
+        passthru = (old.passthru or { }) // {
+          # Let users in the repl inspect the config
+          nixosConfig = config;
+          nixosOptions = options;
+        };
       });
 
     system = {
diff --git a/nixos/modules/profiles/minimal.nix b/nixos/modules/profiles/minimal.nix
index bd1b2b4521899..b76740f7cc587 100644
--- a/nixos/modules/profiles/minimal.nix
+++ b/nixos/modules/profiles/minimal.nix
@@ -18,6 +18,17 @@ with lib;
 
   documentation.nixos.enable = mkDefault false;
 
+  # Perl is a default package.
+  environment.defaultPackages = mkDefault [ ];
+
+  environment.stub-ld.enable = false;
+
+  # The lessopen package pulls in Perl.
+  programs.less.lessopen = mkDefault null;
+
+  # This pulls in nixos-containers which depends on Perl.
+  boot.enableContainers = mkDefault false;
+
   programs.command-not-found.enable = mkDefault false;
 
   services.logrotate.enable = mkDefault false;
diff --git a/nixos/modules/profiles/perlless.nix b/nixos/modules/profiles/perlless.nix
new file mode 100644
index 0000000000000..90abd14f077e4
--- /dev/null
+++ b/nixos/modules/profiles/perlless.nix
@@ -0,0 +1,31 @@
+# WARNING: If you enable this profile, you will NOT be able to switch to a new
+# configuration and thus you will not be able to rebuild your system with
+# nixos-rebuild!
+
+{ lib, ... }:
+
+{
+
+  # Disable switching to a new configuration. This is not a necessary
+  # limitation of a perlless system but just a current one. In the future,
+  # perlless switching might be possible.
+  system.switch.enable = lib.mkDefault false;
+
+  # Remove perl from activation
+  boot.initrd.systemd.enable = lib.mkDefault true;
+  system.etc.overlay.enable = lib.mkDefault true;
+  systemd.sysusers.enable = lib.mkDefault true;
+
+  # Random perl remnants
+  system.disableInstallerTools = lib.mkDefault true;
+  programs.less.lessopen = lib.mkDefault null;
+  programs.command-not-found.enable = lib.mkDefault false;
+  boot.enableContainers = lib.mkDefault false;
+  environment.defaultPackages = lib.mkDefault [ ];
+  documentation.info.enable = lib.mkDefault false;
+
+  # Check that the system does not contain a Nix store path that contains the
+  # string "perl".
+  system.forbiddenDependenciesRegex = "perl";
+
+}
diff --git a/nixos/modules/programs/_1password-gui.nix b/nixos/modules/programs/_1password-gui.nix
index 27c0d34a2eedf..83ef6037fb5a3 100644
--- a/nixos/modules/programs/_1password-gui.nix
+++ b/nixos/modules/programs/_1password-gui.nix
@@ -27,7 +27,7 @@ in
         '';
       };
 
-      package = mkPackageOptionMD pkgs "1Password GUI" {
+      package = mkPackageOption pkgs "1Password GUI" {
         default = [ "_1password-gui" ];
       };
     };
diff --git a/nixos/modules/programs/_1password.nix b/nixos/modules/programs/_1password.nix
index 8537484c7e67d..91246150755d5 100644
--- a/nixos/modules/programs/_1password.nix
+++ b/nixos/modules/programs/_1password.nix
@@ -18,7 +18,7 @@ in
     programs._1password = {
       enable = mkEnableOption (lib.mdDoc "the 1Password CLI tool");
 
-      package = mkPackageOptionMD pkgs "1Password CLI" {
+      package = mkPackageOption pkgs "1Password CLI" {
         default = [ "_1password" ];
       };
     };
diff --git a/nixos/modules/programs/atop.nix b/nixos/modules/programs/atop.nix
index a5f4d990bdbe1..003cfdbfc8fad 100644
--- a/nixos/modules/programs/atop.nix
+++ b/nixos/modules/programs/atop.nix
@@ -16,14 +16,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "Atop");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.atop;
-        defaultText = literalExpression "pkgs.atop";
-        description = lib.mdDoc ''
-          Which package to use for Atop.
-        '';
-      };
+      package = mkPackageOption pkgs "atop" { };
 
       netatop = {
         enable = mkOption {
@@ -144,6 +137,7 @@ in
                 atop.preStart = ''
                   set -e -u
                   shopt -s nullglob
+                  rm -f "$LOGPATH"/atop_*.new
                   for logfile in "$LOGPATH"/atop_*
                   do
                     ${atop}/bin/atopconvert "$logfile" "$logfile".new
@@ -151,9 +145,9 @@ in
                     # 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"
+                      mv -v -f "$logfile".new "$logfile"
                     else
-                      ${pkgs.coreutils}/bin/rm -f "$logfile".new
+                      rm -f "$logfile".new
                     fi
                   done
                 '';
diff --git a/nixos/modules/programs/ausweisapp.nix b/nixos/modules/programs/ausweisapp.nix
index ef1f059568c6a..91870df20246d 100644
--- a/nixos/modules/programs/ausweisapp.nix
+++ b/nixos/modules/programs/ausweisapp.nix
@@ -7,11 +7,11 @@ let
 in
 {
   options.programs.ausweisapp = {
-    enable = mkEnableOption (lib.mdDoc "AusweisApp2");
+    enable = mkEnableOption (lib.mdDoc "AusweisApp");
 
     openFirewall = mkOption {
       description = lib.mdDoc ''
-        Whether to open the required firewall ports for the Smartphone as Card Reader (SaC) functionality of AusweisApp2.
+        Whether to open the required firewall ports for the Smartphone as Card Reader (SaC) functionality of AusweisApp.
       '';
       default = false;
       type = lib.types.bool;
@@ -19,7 +19,7 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = with pkgs; [ AusweisApp2 ];
+    environment.systemPackages = with pkgs; [ ausweisapp ];
     networking.firewall.allowedUDPPorts = lib.optionals cfg.openFirewall [ 24727 ];
   };
 }
diff --git a/nixos/modules/programs/bandwhich.nix b/nixos/modules/programs/bandwhich.nix
index 8d1612217ad87..aa6a0dfb6ffd1 100644
--- a/nixos/modules/programs/bandwhich.nix
+++ b/nixos/modules/programs/bandwhich.nix
@@ -24,7 +24,7 @@ in {
     security.wrappers.bandwhich = {
       owner = "root";
       group = "root";
-      capabilities = "cap_net_raw,cap_net_admin+ep";
+      capabilities = "cap_sys_ptrace,cap_dac_read_search,cap_net_raw,cap_net_admin+ep";
       source = "${pkgs.bandwhich}/bin/bandwhich";
     };
   };
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 286faeadc489d..7d3322ea5e50f 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -81,7 +81,7 @@ in
           if [ "$TERM" != "dumb" ] || [ -n "$INSIDE_EMACS" ]; then
             PROMPT_COLOR="1;31m"
             ((UID)) && PROMPT_COLOR="1;32m"
-            if [ -n "$INSIDE_EMACS" ] || [ "$TERM" = "eterm" ] || [ "$TERM" = "eterm-color" ]; then
+            if [ -n "$INSIDE_EMACS" ]; then
               # Emacs term mode doesn't support xterm title escape sequence (\e]0;)
               PS1="\n\[\033[$PROMPT_COLOR\][\u@\h:\w]\\$\[\033[0m\] "
             else
diff --git a/nixos/modules/programs/browserpass.nix b/nixos/modules/programs/browserpass.nix
index 346d38e5e8803..a9670a37e618c 100644
--- a/nixos/modules/programs/browserpass.nix
+++ b/nixos/modules/programs/browserpass.nix
@@ -27,6 +27,6 @@ with lib;
       "opt/brave/native-messaging-hosts/${appId}".source = source "hosts/chromium";
       "opt/brave/policies/managed/${appId}".source = source "policies/chromium";
     };
-    nixpkgs.config.firefox.enableBrowserpass = true;
+    programs.firefox.nativeMessagingHosts.packages = [ pkgs.browserpass ];
   };
 }
diff --git a/nixos/modules/programs/calls.nix b/nixos/modules/programs/calls.nix
index 7a18982915a9f..3d757bc1fc320 100644
--- a/nixos/modules/programs/calls.nix
+++ b/nixos/modules/programs/calls.nix
@@ -8,7 +8,7 @@ in {
   options = {
     programs.calls = {
       enable = mkEnableOption (lib.mdDoc ''
-        Whether to enable GNOME calls: a phone dialer and call handler.
+        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 032c0e71f1f42..1c3ee7638ee02 100644
--- a/nixos/modules/programs/captive-browser.nix
+++ b/nixos/modules/programs/captive-browser.nix
@@ -5,7 +5,8 @@ let
 
   inherit (lib)
     concatStringsSep escapeShellArgs optionalString
-    literalExpression mkEnableOption mkIf mkOption mkOptionDefault types;
+    literalExpression mkEnableOption mkPackageOption mkIf mkOption
+    mkOptionDefault types;
 
   requiresSetcapWrapper = config.boot.kernelPackages.kernelOlder "5.7" && cfg.bindInterface;
 
@@ -50,12 +51,7 @@ in
     programs.captive-browser = {
       enable = mkEnableOption (lib.mdDoc "captive browser");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.captive-browser;
-        defaultText = literalExpression "pkgs.captive-browser";
-        description = lib.mdDoc "Which package to use for captive-browser";
-      };
+      package = mkPackageOption pkgs "captive-browser" { };
 
       interface = mkOption {
         type = types.str;
diff --git a/nixos/modules/programs/cdemu.nix b/nixos/modules/programs/cdemu.nix
index d43f009f2f92e..7eba4d29d83ba 100644
--- a/nixos/modules/programs/cdemu.nix
+++ b/nixos/modules/programs/cdemu.nix
@@ -53,6 +53,19 @@ in {
       dbus.packages = [ pkgs.cdemu-daemon ];
     };
 
+    users.groups.${config.programs.cdemu.group} = {};
+
+    # Systemd User service
+    # manually adapted from example in source package:
+    # https://sourceforge.net/p/cdemu/code/ci/master/tree/cdemu-daemon/service-example/cdemu-daemon.service
+    systemd.user.services.cdemu-daemon.description = "CDEmu daemon";
+    systemd.user.services.cdemu-daemon.serviceConfig = {
+      Type = "dbus";
+      BusName = "net.sf.cdemu.CDEmuDaemon";
+      ExecStart = "${pkgs.cdemu-daemon}/bin/cdemu-daemon --config-file \"%h/.config/cdemu-daemon\"";
+      Restart = "no";
+    };
+
     environment.systemPackages =
       [ pkgs.cdemu-daemon pkgs.cdemu-client ]
       ++ optional cfg.gui pkgs.gcdemu
diff --git a/nixos/modules/programs/clash-verge.nix b/nixos/modules/programs/clash-verge.nix
index 29977be3858f0..57a1c0377edbf 100644
--- a/nixos/modules/programs/clash-verge.nix
+++ b/nixos/modules/programs/clash-verge.nix
@@ -2,17 +2,9 @@
 
 {
   options.programs.clash-verge = {
-    enable = lib.mkEnableOption (lib.mdDoc ''
-      Clash Verge.
-    '');
-
-    autoStart = lib.mkEnableOption (lib.mdDoc ''
-      Clash Verge Auto Launch.
-    '');
-
-    tunMode = lib.mkEnableOption (lib.mdDoc ''
-      Clash Verge Tun Mode.
-    '');
+    enable = lib.mkEnableOption (lib.mdDoc "Clash Verge");
+    autoStart = lib.mkEnableOption (lib.mdDoc "Clash Verge auto launch");
+    tunMode = lib.mkEnableOption (lib.mdDoc "Clash Verge TUN mode");
   };
 
   config =
diff --git a/nixos/modules/programs/cnping.nix b/nixos/modules/programs/cnping.nix
index d3cf659d4297f..143267fc9a426 100644
--- a/nixos/modules/programs/cnping.nix
+++ b/nixos/modules/programs/cnping.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     programs.cnping = {
-      enable = mkEnableOption (lib.mdDoc "Whether to install a setcap wrapper for cnping");
+      enable = mkEnableOption (lib.mdDoc "a setcap wrapper for cnping");
     };
   };
 
diff --git a/nixos/modules/programs/darling.nix b/nixos/modules/programs/darling.nix
index c4e1c73b5c29a..589a9dd5d6037 100644
--- a/nixos/modules/programs/darling.nix
+++ b/nixos/modules/programs/darling.nix
@@ -6,7 +6,7 @@ in {
   options = {
     programs.darling = {
       enable = lib.mkEnableOption (lib.mdDoc "Darling, a Darwin/macOS compatibility layer for Linux");
-      package = lib.mkPackageOptionMD pkgs "darling" {};
+      package = lib.mkPackageOption pkgs "darling" {};
     };
   };
 
diff --git a/nixos/modules/programs/digitalbitbox/default.nix b/nixos/modules/programs/digitalbitbox/default.nix
index 5ee6cdafe63a0..bdacbc010c410 100644
--- a/nixos/modules/programs/digitalbitbox/default.nix
+++ b/nixos/modules/programs/digitalbitbox/default.nix
@@ -16,11 +16,10 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.digitalbitbox;
-      defaultText = literalExpression "pkgs.digitalbitbox";
-      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.";
+    package = mkPackageOption pkgs "digitalbitbox" {
+      extraDescription = ''
+        This can be used to install a package with udev rules that differ from the defaults.
+      '';
     };
   };
 
diff --git a/nixos/modules/programs/direnv.nix b/nixos/modules/programs/direnv.nix
index 53717fae11a02..fdc646eb4b164 100644
--- a/nixos/modules/programs/direnv.nix
+++ b/nixos/modules/programs/direnv.nix
@@ -11,10 +11,10 @@ in {
     enable = lib.mkEnableOption (lib.mdDoc ''
       direnv integration. Takes care of both installation and
       setting up the sourcing of the shell. Additionally enables nix-direnv
-      integration. Note that you need to logout and login for this change to apply.
+      integration. Note that you need to logout and login for this change to apply
     '');
 
-    package = lib.mkPackageOptionMD pkgs "direnv" {};
+    package = lib.mkPackageOption pkgs "direnv" {};
 
     direnvrcExtra = lib.mkOption {
       type = lib.types.lines;
@@ -32,15 +32,6 @@ in {
       the hiding of direnv logging
     '');
 
-    persistDerivations =
-      (lib.mkEnableOption (lib.mdDoc ''
-        setting keep-derivations and keep-outputs to true
-        to prevent shells from getting garbage collected
-      ''))
-      // {
-        default = true;
-      };
-
     loadInNixShell =
       lib.mkEnableOption (lib.mdDoc ''
         loading direnv in `nix-shell` `nix shell` or `nix develop`
@@ -58,10 +49,21 @@ in {
           default = true;
         };
 
-      package = lib.mkPackageOptionMD pkgs "nix-direnv" {};
+      package = lib.mkOption {
+        default = pkgs.nix-direnv.override { nix = config.nix.package; };
+        defaultText = "pkgs.nix-direnv";
+        type = lib.types.package;
+        description = lib.mdDoc ''
+          The nix-direnv package to use
+        '';
+      };
     };
   };
 
+  imports = [
+    (lib.mkRemovedOptionModule ["programs" "direnv" "persistDerivations"] "persistDerivations was removed as it is no longer necessary")
+  ];
+
   config = lib.mkIf cfg.enable {
 
     programs = {
@@ -87,11 +89,6 @@ in {
       '';
     };
 
-    nix.settings = lib.mkIf cfg.persistDerivations {
-      keep-outputs = true;
-      keep-derivations = true;
-    };
-
     environment = {
       systemPackages =
         if cfg.loadInNixShell then [cfg.package]
diff --git a/nixos/modules/programs/dmrconfig.nix b/nixos/modules/programs/dmrconfig.nix
index 20a0dc9556daa..29268cdfeb505 100644
--- a/nixos/modules/programs/dmrconfig.nix
+++ b/nixos/modules/programs/dmrconfig.nix
@@ -21,12 +21,7 @@ in {
         relatedPackages = [ "dmrconfig" ];
       };
 
-      package = mkOption {
-        default = pkgs.dmrconfig;
-        type = types.package;
-        defaultText = literalExpression "pkgs.dmrconfig";
-        description = lib.mdDoc "dmrconfig derivation to use";
-      };
+      package = mkPackageOption pkgs "dmrconfig" { };
     };
   };
 
diff --git a/nixos/modules/programs/dublin-traceroute.nix b/nixos/modules/programs/dublin-traceroute.nix
new file mode 100644
index 0000000000000..cfcd6e8308ff5
--- /dev/null
+++ b/nixos/modules/programs/dublin-traceroute.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.dublin-traceroute;
+
+in {
+  meta.maintainers = pkgs.dublin-traceroute.meta.maintainers;
+
+  options = {
+    programs.dublin-traceroute = {
+      enable = mkEnableOption (mdDoc ''
+      dublin-traceroute, add it to the global environment and configure a setcap wrapper for it.
+      '');
+
+      package = mkPackageOption pkgs "dublin-traceroute" { };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    security.wrappers.dublin-traceroute = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_net_raw+p";
+      source = getExe cfg.package;
+    };
+  };
+}
diff --git a/nixos/modules/programs/ecryptfs.nix b/nixos/modules/programs/ecryptfs.nix
new file mode 100644
index 0000000000000..63c1a3ad44199
--- /dev/null
+++ b/nixos/modules/programs/ecryptfs.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.ecryptfs;
+
+in {
+  options.programs.ecryptfs = {
+    enable = mkEnableOption (lib.mdDoc "ecryptfs setuid mount wrappers");
+  };
+
+  config = mkIf cfg.enable {
+    security.wrappers = {
+
+      "mount.ecryptfs_private" = {
+        setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${lib.getBin pkgs.ecryptfs}/bin/mount.ecryptfs_private";
+      };
+      "umount.ecryptfs_private" = {
+        setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${lib.getBin pkgs.ecryptfs}/bin/umount.ecryptfs_private";
+      };
+
+    };
+  };
+}
diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix
index 3fbda153e0b44..8ac723f42f61a 100644
--- a/nixos/modules/programs/environment.nix
+++ b/nixos/modules/programs/environment.nix
@@ -22,7 +22,6 @@ in
         # be specified here; do so in the default value of programs.less.envVariables instead
         PAGER = mkDefault "less";
         EDITOR = mkDefault "nano";
-        XDG_CONFIG_DIRS = [ "/etc/xdg" ]; # needs to be before profile-relative paths to allow changes through environment.etc
       };
 
     # since we set PAGER to this above, make sure it's installed
@@ -33,18 +32,20 @@ in
         "/run/current-system/sw"
       ];
 
+    environment.sessionVariables =
+      {
+        XDG_CONFIG_DIRS = [ "/etc/xdg" ]; # needs to be before profile-relative paths to allow changes through environment.etc
+      };
+
     # TODO: move most of these elsewhere
     environment.profileRelativeSessionVariables =
       { PATH = [ "/bin" ];
         INFOPATH = [ "/info" "/share/info" ];
-        KDEDIRS = [ "" ];
-        QT_PLUGIN_PATH = [ "/lib/qt4/plugins" "/lib/kde4/plugins" ];
         QTWEBKIT_PLUGIN_PATH = [ "/lib/mozilla/plugins/" ];
         GTK_PATH = [ "/lib/gtk-2.0" "/lib/gtk-3.0" "/lib/gtk-4.0" ];
         XDG_CONFIG_DIRS = [ "/etc/xdg" ];
         XDG_DATA_DIRS = [ "/share" ];
-        MOZ_PLUGIN_PATH = [ "/lib/mozilla/plugins" ];
-        LIBEXEC_PATH = [ "/lib/libexec" ];
+        LIBEXEC_PATH = [ "/libexec" ];
       };
 
     environment.pathsToLink = [ "/lib/gtk-2.0" "/lib/gtk-3.0" "/lib/gtk-4.0" ];
diff --git a/nixos/modules/programs/evince.nix b/nixos/modules/programs/evince.nix
index 9ed5ea0feb04b..ed543d35cc5e4 100644
--- a/nixos/modules/programs/evince.nix
+++ b/nixos/modules/programs/evince.nix
@@ -24,12 +24,7 @@ in {
       enable = mkEnableOption
         (lib.mdDoc "Evince, the GNOME document viewer");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.evince;
-        defaultText = literalExpression "pkgs.evince";
-        description = lib.mdDoc "Evince derivation to use.";
-      };
+      package = mkPackageOption pkgs "evince" { };
 
     };
 
diff --git a/nixos/modules/programs/feedbackd.nix b/nixos/modules/programs/feedbackd.nix
index cee8daa314622..010287e5cd565 100644
--- a/nixos/modules/programs/feedbackd.nix
+++ b/nixos/modules/programs/feedbackd.nix
@@ -8,18 +8,11 @@ in {
   options = {
     programs.feedbackd = {
       enable = mkEnableOption (lib.mdDoc ''
-        Whether to enable the feedbackd D-BUS service and udev rules.
+        the feedbackd D-BUS service and udev rules.
 
-        Your user needs to be in the `feedbackd` group to trigger effects.
+        Your user needs to be in the `feedbackd` group to trigger effects
       '');
-      package = mkOption {
-        description = lib.mdDoc ''
-          Which feedbackd package to use.
-        '';
-        type = types.package;
-        default = pkgs.feedbackd;
-        defaultText = literalExpression "pkgs.feedbackd";
-      };
+      package = mkPackageOption pkgs "feedbackd" { };
     };
   };
   config = mkIf cfg.enable {
diff --git a/nixos/modules/programs/file-roller.nix b/nixos/modules/programs/file-roller.nix
index ca0c4d1b2a2a0..a343d4a261c98 100644
--- a/nixos/modules/programs/file-roller.nix
+++ b/nixos/modules/programs/file-roller.nix
@@ -23,12 +23,7 @@ in {
 
       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 = lib.mdDoc "File Roller derivation to use.";
-      };
+      package = mkPackageOption pkgs [ "gnome" "file-roller" ] { };
 
     };
 
diff --git a/nixos/modules/programs/firefox.nix b/nixos/modules/programs/firefox.nix
index 8653f066cf8fd..29c567783e27a 100644
--- a/nixos/modules/programs/firefox.nix
+++ b/nixos/modules/programs/firefox.nix
@@ -5,8 +5,6 @@ with lib;
 let
   cfg = config.programs.firefox;
 
-  nmh = cfg.nativeMessagingHosts;
-
   policyFormat = pkgs.formats.json { };
 
   organisationInfo = ''
@@ -17,6 +15,50 @@ let
     given control of your browser, unless of course they also control your
     NixOS configuration.
   '';
+
+  # deprecated per-native-messaging-host options
+  nmhOptions = {
+    browserpass = {
+      name = "Browserpass";
+      package = pkgs.browserpass;
+    };
+    bukubrow = {
+      name = "Bukubrow";
+      package = pkgs.bukubrow;
+    };
+    euwebid = {
+      name = "Web eID";
+      package = pkgs.web-eid-app;
+    };
+    ff2mpv = {
+      name = "ff2mpv";
+      package = pkgs.ff2mpv;
+    };
+    fxCast = {
+      name = "fx_cast";
+      package = pkgs.fx-cast-bridge;
+    };
+    gsconnect = {
+      name = "GSConnect";
+      package = pkgs.gnomeExtensions.gsconnect;
+    };
+    jabref = {
+      name = "JabRef";
+      package = pkgs.jabref;
+    };
+    passff = {
+      name = "PassFF";
+      package = pkgs.passff-host;
+    };
+    tridactyl = {
+      name = "Tridactyl";
+      package = pkgs.tridactyl-native;
+    };
+    ugetIntegrator = {
+      name = "Uget Integrator";
+      package = pkgs.uget-integrator;
+    };
+  };
 in
 {
   options.programs.firefox = {
@@ -36,13 +78,19 @@ in
       ];
     };
 
+    wrapperConfig = mkOption {
+      type = types.attrs;
+      default = {};
+      description = mdDoc "Arguments to pass to Firefox wrapper";
+    };
+
     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)
+        See [Mozilla's documentation](https://mozilla.github.io/policy-templates/)
         for a list of available options.
 
         This can be used to install extensions declaratively! Check out the
@@ -198,46 +246,34 @@ in
       '';
     };
 
-    nativeMessagingHosts = mapAttrs (_: v: mkEnableOption (mdDoc v)) {
-      browserpass = "Browserpass support";
-      bukubrow = "Bukubrow support";
-      euwebid = "Web eID support";
-      ff2mpv = "ff2mpv support";
-      fxCast = "fx_cast support";
-      gsconnect = "GSConnect support";
-      jabref = "JabRef support";
-      passff = "PassFF support";
-      tridactyl = "Tridactyl support";
-      ugetIntegrator = "Uget Integrator support";
-    };
+    nativeMessagingHosts = ({
+      packages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = mdDoc ''
+          Additional packages containing native messaging hosts that should be made available to Firefox extensions.
+        '';
+      };
+    }) // (mapAttrs (k: v: mkEnableOption (mdDoc "${v.name} support")) nmhOptions);
   };
 
-  config = mkIf cfg.enable {
+  config = let
+    forEachEnabledNmh = fn: flatten (mapAttrsToList (k: v: lib.optional cfg.nativeMessagingHosts.${k} (fn k v)) nmhOptions);
+  in mkIf cfg.enable {
+    warnings = forEachEnabledNmh (k: v:
+      "The `programs.firefox.nativeMessagingHosts.${k}` option is deprecated, " +
+      "please add `${v.package.pname}` to `programs.firefox.nativeMessagingHosts.packages` instead."
+    );
+    programs.firefox.nativeMessagingHosts.packages = forEachEnabledNmh (_: v: v.package);
+
     environment.systemPackages = [
-      (cfg.package.override {
-        extraPrefs = cfg.autoConfig;
-        extraNativeMessagingHosts = with pkgs; optionals nmh.ff2mpv [
-          ff2mpv
-        ] ++ optionals nmh.euwebid [
-          web-eid-app
-        ] ++ optionals nmh.gsconnect [
-          gnomeExtensions.gsconnect
-        ] ++ optionals nmh.jabref [
-          jabref
-        ] ++ optionals nmh.passff [
-          passff-host
-        ];
-      })
+      (cfg.package.override (old: {
+        extraPrefsFiles = old.extraPrefsFiles or [] ++ [(pkgs.writeText "firefox-autoconfig.js" cfg.autoConfig)];
+        nativeMessagingHosts = old.nativeMessagingHosts or [] ++ cfg.nativeMessagingHosts.packages;
+        cfg = (old.cfg or {}) // cfg.wrapperConfig;
+      }))
     ];
 
-    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; };
@@ -248,6 +284,7 @@ in
 
     # Preferences are converted into a policy
     programs.firefox.policies = {
+      DisableAppUpdate = true;
       Preferences = (mapAttrs
         (_: value: { Value = value; Status = cfg.preferencesStatus; })
         cfg.preferences);
diff --git a/nixos/modules/programs/firejail.nix b/nixos/modules/programs/firejail.nix
index 6f79c13d94b44..046c31ce64f6b 100644
--- a/nixos/modules/programs/firejail.nix
+++ b/nixos/modules/programs/firejail.nix
@@ -53,7 +53,7 @@ in {
           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.";
+            description = lib.mdDoc ".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 {
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index c85097f45e920..a4c20560bc9b6 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -208,7 +208,7 @@ in
         end
 
         # if we haven't sourced the login config, do it
-        status --is-login; and not set -q __fish_nixos_login_config_sourced
+        status is-login; and not set -q __fish_nixos_login_config_sourced
         and begin
           ${sourceEnv "loginShellInit"}
 
@@ -220,7 +220,7 @@ in
         end
 
         # if we haven't sourced the interactive config, do it
-        status --is-interactive; and not set -q __fish_nixos_interactive_config_sourced
+        status is-interactive; and not set -q __fish_nixos_interactive_config_sourced
         and begin
           ${fishAbbrs}
           ${fishAliases}
@@ -258,20 +258,17 @@ in
             preferLocalBuild = true;
             allowSubstitutes = false;
           };
-          generateCompletions = package: pkgs.runCommand
-            "${package.name}_fish-completions"
-            (
-              {
-                inherit package;
-                preferLocalBuild = true;
-                allowSubstitutes = false;
-              }
-              // optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; }
-            )
+          generateCompletions = package: pkgs.runCommandLocal
+            ( with lib.strings; let
+                storeLength = stringLength storeDir + 34; # Nix' StorePath::HashLen + 2 for the separating slash and dash
+                pathName = substring storeLength (stringLength package - storeLength) package;
+              in (package.name or pathName) + "_fish-completions")
+            ( { inherit package; } //
+              optionalAttrs (package ? meta.priority) { meta.priority = package.meta.priority; })
             ''
               mkdir -p $out
               if [ -d $package/share/man ]; then
-                find $package/share/man -type f | xargs ${pkgs.python3.pythonForBuild.interpreter} ${patchedGenerator}/create_manpage_completions.py --directory $out >/dev/null
+                find $package/share/man -type f | xargs ${pkgs.python3.pythonOnBuildForHost.interpreter} ${patchedGenerator}/create_manpage_completions.py --directory $out >/dev/null
               fi
             '';
         in
diff --git a/nixos/modules/programs/flashrom.nix b/nixos/modules/programs/flashrom.nix
index 9f8faff14e472..f954bc2197b17 100644
--- a/nixos/modules/programs/flashrom.nix
+++ b/nixos/modules/programs/flashrom.nix
@@ -16,7 +16,7 @@ in
         group.
       '';
     };
-    package = mkPackageOptionMD pkgs "flashrom" { };
+    package = mkPackageOption pkgs "flashrom" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/programs/flexoptix-app.nix b/nixos/modules/programs/flexoptix-app.nix
index 2524e7ba4d58a..6f37fe54667c9 100644
--- a/nixos/modules/programs/flexoptix-app.nix
+++ b/nixos/modules/programs/flexoptix-app.nix
@@ -9,12 +9,7 @@ in {
     programs.flexoptix-app = {
       enable = mkEnableOption (lib.mdDoc "FLEXOPTIX app + udev rules");
 
-      package = mkOption {
-        description = lib.mdDoc "FLEXOPTIX app package to use";
-        type = types.package;
-        default = pkgs.flexoptix-app;
-        defaultText = literalExpression "pkgs.flexoptix-app";
-      };
+      package = mkPackageOption pkgs "flexoptix-app" { };
     };
   };
 
diff --git a/nixos/modules/programs/gamemode.nix b/nixos/modules/programs/gamemode.nix
index c43e2c2296f5a..344f392852e2a 100644
--- a/nixos/modules/programs/gamemode.nix
+++ b/nixos/modules/programs/gamemode.nix
@@ -18,7 +18,7 @@ in
 
       settings = mkOption {
         type = settingsFormat.type;
-        default = {};
+        default = { };
         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/gamescope.nix b/nixos/modules/programs/gamescope.nix
index a31295e736df2..594e5be5fd583 100644
--- a/nixos/modules/programs/gamescope.nix
+++ b/nixos/modules/programs/gamescope.nix
@@ -23,14 +23,7 @@ in
   options.programs.gamescope = {
     enable = mkEnableOption (mdDoc "gamescope");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.gamescope;
-      defaultText = literalExpression "pkgs.gamescope";
-      description = mdDoc ''
-        The GameScope package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "gamescope" { };
 
     capSysNice = mkOption {
       type = types.bool;
diff --git a/nixos/modules/programs/git.nix b/nixos/modules/programs/git.nix
index 4e271a8c134b2..8fb69cbae28fe 100644
--- a/nixos/modules/programs/git.nix
+++ b/nixos/modules/programs/git.nix
@@ -11,12 +11,8 @@ in
     programs.git = {
       enable = mkEnableOption (lib.mdDoc "git");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.git;
-        defaultText = literalExpression "pkgs.git";
-        example = literalExpression "pkgs.gitFull";
-        description = lib.mdDoc "The git package to use";
+      package = mkPackageOption pkgs "git" {
+        example = "gitFull";
       };
 
       config = mkOption {
@@ -58,15 +54,14 @@ in
         '';
       };
 
+      prompt = {
+        enable = mkEnableOption "automatically sourcing git-prompt.sh. This does not change $PS1; it simply provides relevant utility functions";
+      };
+
       lfs = {
         enable = mkEnableOption (lib.mdDoc "git-lfs");
 
-        package = mkOption {
-          type = types.package;
-          default = pkgs.git-lfs;
-          defaultText = literalExpression "pkgs.git-lfs";
-          description = lib.mdDoc "The git-lfs package to use";
-        };
+        package = mkPackageOption pkgs "git-lfs" { };
       };
     };
   };
@@ -89,6 +84,11 @@ in
         };
       };
     })
+    (mkIf (cfg.enable && cfg.prompt.enable) {
+      environment.interactiveShellInit = ''
+        source ${cfg.package}/share/bash-completion/completions/git-prompt.sh
+      '';
+    })
   ];
 
   meta.maintainers = with maintainers; [ figsoda ];
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index 697b6e9a0bd03..8f82de0336667 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -6,6 +6,10 @@ let
 
   cfg = config.programs.gnupg;
 
+  agentSettingsFormat = pkgs.formats.keyValue {
+    mkKeyValue = lib.generators.mkKeyValueDefault { } " ";
+  };
+
   xserverCfg = config.services.xserver;
 
   defaultPinentryFlavor =
@@ -25,14 +29,7 @@ in
 {
 
   options.programs.gnupg = {
-    package = mkOption {
-      type = types.package;
-      default = pkgs.gnupg;
-      defaultText = literalExpression "pkgs.gnupg";
-      description = lib.mdDoc ''
-        The gpg package that should be used.
-      '';
-    };
+    package = mkPackageOption pkgs "gnupg" { };
 
     agent.enable = mkOption {
       type = types.bool;
@@ -82,6 +79,18 @@ in
       '';
     };
 
+    agent.settings = mkOption {
+      type = agentSettingsFormat.type;
+      default = { };
+      example = {
+        default-cache-ttl = 600;
+      };
+      description = lib.mdDoc ''
+        Configuration for /etc/gnupg/gpg-agent.conf.
+        See {manpage}`gpg-agent(1)` for supported options.
+      '';
+    };
+
     dirmngr.enable = mkOption {
       type = types.bool;
       default = false;
@@ -92,17 +101,20 @@ in
   };
 
   config = mkIf cfg.agent.enable {
-    environment.etc."gnupg/gpg-agent.conf".text =
-      lib.optionalString (cfg.agent.pinentryFlavor != null) ''
-      pinentry-program ${pkgs.pinentry.${cfg.agent.pinentryFlavor}}/bin/pinentry
-    '';
+    programs.gnupg.agent.settings = {
+      pinentry-program = lib.mkIf (cfg.agent.pinentryFlavor != null)
+        "${pkgs.pinentry.${cfg.agent.pinentryFlavor}}/bin/pinentry";
+    };
+
+    environment.etc."gnupg/gpg-agent.conf".source =
+      agentSettingsFormat.generate "gpg-agent.conf" cfg.agent.settings;
 
     # This overrides the systemd user unit shipped with the gnupg package
     systemd.user.services.gpg-agent = {
       unitConfig = {
         Description = "GnuPG cryptographic agent and passphrase cache";
         Documentation = "man:gpg-agent(1)";
-        Requires = [ "gpg-agent.socket" ];
+        Requires = [ "sockets.target" ];
       };
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/gpg-agent --supervised";
diff --git a/nixos/modules/programs/gpaste.nix b/nixos/modules/programs/gpaste.nix
index 074b4d59a365a..37172c9583a37 100644
--- a/nixos/modules/programs/gpaste.nix
+++ b/nixos/modules/programs/gpaste.nix
@@ -32,5 +32,7 @@ with lib;
     systemd.packages = [ pkgs.gnome.gpaste ];
     # gnome-control-center crashes in Keyboard Shortcuts pane without the GSettings schemas.
     services.xserver.desktopManager.gnome.sessionPath = [ pkgs.gnome.gpaste ];
+    # gpaste-reloaded applet doesn't work without the typelib
+    services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gnome.gpaste ];
   };
 }
diff --git a/nixos/modules/programs/htop.nix b/nixos/modules/programs/htop.nix
index 777ea709836e6..9dbab954b2bbb 100644
--- a/nixos/modules/programs/htop.nix
+++ b/nixos/modules/programs/htop.nix
@@ -18,14 +18,7 @@ in
 {
 
   options.programs.htop = {
-    package = mkOption {
-      type = types.package;
-      default = pkgs.htop;
-      defaultText = lib.literalExpression "pkgs.htop";
-      description = lib.mdDoc ''
-        The htop package that should be used.
-      '';
-    };
+    package = mkPackageOption pkgs "htop" { };
 
     enable = mkEnableOption (lib.mdDoc "htop process monitor");
 
diff --git a/nixos/modules/programs/hyprland.nix b/nixos/modules/programs/hyprland.nix
index e0ee5b6bd2a45..9061ce5da83a8 100644
--- a/nixos/modules/programs/hyprland.nix
+++ b/nixos/modules/programs/hyprland.nix
@@ -7,9 +7,7 @@ with lib; let
   cfg = config.programs.hyprland;
 
   finalPortalPackage = cfg.portalPackage.override {
-    hyprland-share-picker = pkgs.hyprland-share-picker.override {
-      hyprland = cfg.finalPackage;
-    };
+    hyprland = cfg.finalPackage;
   };
 in
 {
@@ -25,14 +23,13 @@ in
       '';
     };
 
-    package = mkPackageOptionMD pkgs "hyprland" { };
+    package = mkPackageOption pkgs "hyprland" { };
 
     finalPackage = mkOption {
       type = types.package;
       readOnly = true;
       default = cfg.package.override {
         enableXWayland = cfg.xwayland.enable;
-        enableNvidiaPatches = cfg.enableNvidiaPatches;
       };
       defaultText = literalExpression
         "`programs.hyprland.package` with applied configuration";
@@ -41,11 +38,9 @@ in
       '';
     };
 
-    portalPackage = mkPackageOptionMD pkgs "xdg-desktop-portal-hyprland" { };
+    portalPackage = mkPackageOption pkgs "xdg-desktop-portal-hyprland" { };
 
     xwayland.enable = mkEnableOption (mdDoc "XWayland") // { default = true; };
-
-    enableNvidiaPatches = mkEnableOption (mdDoc "patching wlroots for better Nvidia support");
   };
 
   config = mkIf cfg.enable {
@@ -66,6 +61,7 @@ in
     xdg.portal = {
       enable = mkDefault true;
       extraPortals = [ finalPortalPackage ];
+      configPackages = mkDefault [ cfg.finalPackage ];
     };
   };
 
@@ -74,9 +70,13 @@ in
       [ "programs" "hyprland" "xwayland" "hidpi" ]
       "XWayland patches are deprecated. Refer to https://wiki.hyprland.org/Configuring/XWayland"
     )
-    (mkRenamedOptionModule
-      [ "programs" "hyprland" "nvidiaPatches" ]
+    (mkRemovedOptionModule
       [ "programs" "hyprland" "enableNvidiaPatches" ]
+      "Nvidia patches are no longer needed"
+    )
+    (mkRemovedOptionModule
+      [ "programs" "hyprland" "nvidiaPatches" ]
+      "Nvidia patches are no longer needed"
     )
   ];
 }
diff --git a/nixos/modules/programs/i3lock.nix b/nixos/modules/programs/i3lock.nix
index 466ae59c9277f..44e2e04c2799d 100644
--- a/nixos/modules/programs/i3lock.nix
+++ b/nixos/modules/programs/i3lock.nix
@@ -13,16 +13,12 @@ in {
   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,
+      package = mkPackageOption pkgs "i3lock" {
+        example = "i3lock-color";
+        extraDescription = ''
+          ::: {.note}
           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 {
diff --git a/nixos/modules/programs/iay.nix b/nixos/modules/programs/iay.nix
index 9164f5cb64862..1fa00e43795ad 100644
--- a/nixos/modules/programs/iay.nix
+++ b/nixos/modules/programs/iay.nix
@@ -2,11 +2,11 @@
 
 let
   cfg = config.programs.iay;
-  inherit (lib) mkEnableOption mkIf mkOption mkPackageOptionMD optionalString types;
+  inherit (lib) mkEnableOption mkIf mkOption mkPackageOption optionalString types;
 in {
   options.programs.iay = {
     enable = mkEnableOption (lib.mdDoc "iay");
-    package = mkPackageOptionMD pkgs "iay" {};
+    package = mkPackageOption pkgs "iay" {};
 
     minimalPrompt = mkOption {
       type = types.bool;
diff --git a/nixos/modules/programs/java.nix b/nixos/modules/programs/java.nix
index c5f83858d06a3..251192183ebf0 100644
--- a/nixos/modules/programs/java.nix
+++ b/nixos/modules/programs/java.nix
@@ -30,13 +30,8 @@ in
         '';
       };
 
-      package = mkOption {
-        default = pkgs.jdk;
-        defaultText = literalExpression "pkgs.jdk";
-        description = lib.mdDoc ''
-          Java package to install. Typical values are pkgs.jdk or pkgs.jre.
-        '';
-        type = types.package;
+      package = mkPackageOption pkgs "jdk" {
+        example = "jre";
       };
 
       binfmt = mkEnableOption (lib.mdDoc "binfmt to execute java jar's and classes");
diff --git a/nixos/modules/programs/joycond-cemuhook.nix b/nixos/modules/programs/joycond-cemuhook.nix
new file mode 100644
index 0000000000000..7b129868db28c
--- /dev/null
+++ b/nixos/modules/programs/joycond-cemuhook.nix
@@ -0,0 +1,17 @@
+{ lib, pkgs, config, ... }:
+with lib;
+{
+  options.programs.joycond-cemuhook = {
+    enable = mkEnableOption (lib.mdDoc "joycond-cemuhook, a program to enable support for cemuhook's UDP protocol for joycond devices.");
+  };
+
+  config = lib.mkIf config.programs.joycond-cemuhook.enable {
+    assertions = [
+      {
+        assertion = config.services.joycond.enable;
+        message = "joycond must be enabled through `services.joycond.enable`";
+      }
+    ];
+    environment.systemPackages = [ pkgs.joycond-cemuhook ];
+  };
+}
diff --git a/nixos/modules/programs/k40-whisperer.nix b/nixos/modules/programs/k40-whisperer.nix
index 27a79caa4b538..96cf159f2cf7d 100644
--- a/nixos/modules/programs/k40-whisperer.nix
+++ b/nixos/modules/programs/k40-whisperer.nix
@@ -20,15 +20,7 @@ in
       default = "k40";
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.k40-whisperer;
-      defaultText = literalExpression "pkgs.k40-whisperer";
-      example = literalExpression "pkgs.k40-whisperer";
-      description = lib.mdDoc ''
-        K40 Whisperer package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "k40-whisperer" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/programs/kdeconnect.nix b/nixos/modules/programs/kdeconnect.nix
index 4978c428ce341..a16fad03eefe0 100644
--- a/nixos/modules/programs/kdeconnect.nix
+++ b/nixos/modules/programs/kdeconnect.nix
@@ -9,16 +9,10 @@ with lib;
       1714 to 1764 as they are needed for it to function properly.
       You can use the {option}`package` to use
       `gnomeExtensions.gsconnect` as an alternative
-      implementation if you use Gnome.
+      implementation if you use Gnome
     '');
-    package = mkOption {
-      default = pkgs.plasma5Packages.kdeconnect-kde;
-      defaultText = literalExpression "pkgs.plasma5Packages.kdeconnect-kde";
-      type = types.package;
-      example = literalExpression "pkgs.gnomeExtensions.gsconnect";
-      description = lib.mdDoc ''
-        The package providing the implementation for kdeconnect.
-      '';
+    package = mkPackageOption pkgs [ "plasma5Packages" "kdeconnect-kde" ] {
+      example = "gnomeExtensions.gsconnect";
     };
   };
   config =
diff --git a/nixos/modules/programs/mininet.nix b/nixos/modules/programs/mininet.nix
index 02272729d233c..3568736854d8e 100644
--- a/nixos/modules/programs/mininet.nix
+++ b/nixos/modules/programs/mininet.nix
@@ -5,27 +5,7 @@
 with lib;
 
 let
-  cfg  = config.programs.mininet;
-
-  generatedPath = with pkgs; makeSearchPath "bin"  [
-    iperf ethtool iproute2 socat
-  ];
-
-  pyEnv = pkgs.python.withPackages(ps: [ ps.mininet-python ]);
-
-  mnexecWrapped = pkgs.runCommand "mnexec-wrapper"
-    { nativeBuildInputs = [ pkgs.makeWrapper pkgs.pythonPackages.wrapPython ]; }
-    ''
-      makeWrapper ${pkgs.mininet}/bin/mnexec \
-        $out/bin/mnexec \
-        --prefix PATH : "${generatedPath}"
-
-      ln -s ${pyEnv}/bin/mn $out/bin/mn
-
-      # mn errors out without a telnet binary
-      # pkgs.inetutils brings an undesired ifconfig into PATH see #43105
-      ln -s ${pkgs.inetutils}/bin/telnet $out/bin/telnet
-    '';
+  cfg = config.programs.mininet;
 in
 {
   options.programs.mininet.enable = mkEnableOption (lib.mdDoc "Mininet");
@@ -34,6 +14,6 @@ in
 
     virtualisation.vswitch.enable = true;
 
-    environment.systemPackages = [ mnexecWrapped ];
+    environment.systemPackages = [ pkgs.mininet ];
   };
 }
diff --git a/nixos/modules/programs/minipro.nix b/nixos/modules/programs/minipro.nix
index a947f83f2ee05..8cb64866a84cc 100644
--- a/nixos/modules/programs/minipro.nix
+++ b/nixos/modules/programs/minipro.nix
@@ -13,7 +13,7 @@ in
         '';
       };
 
-      package = lib.mkPackageOptionMD pkgs "minipro" { };
+      package = lib.mkPackageOption pkgs "minipro" { };
     };
   };
 
diff --git a/nixos/modules/programs/mosh.nix b/nixos/modules/programs/mosh.nix
index 9e56e1731d7cc..593246ab6dcd1 100644
--- a/nixos/modules/programs/mosh.nix
+++ b/nixos/modules/programs/mosh.nix
@@ -1,7 +1,5 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
 
   cfg  = config.programs.mosh;
@@ -9,28 +7,26 @@ let
 in
 {
   options.programs.mosh = {
-    enable = mkOption {
-      description = lib.mdDoc ''
-        Whether to enable mosh. Note, this will open ports in your firewall!
-      '';
-      default = false;
-      type = lib.types.bool;
+    enable = lib.mkEnableOption "mosh";
+    openFirewall = lib.mkEnableOption "" // {
+      description = "Whether to automatically open the necessary ports in the firewall.";
+      default = true;
     };
-    withUtempter = mkOption {
+    withUtempter = lib.mkEnableOption "" // {
       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!
       '';
       default = true;
-      type = lib.types.bool;
     };
   };
 
-  config = mkIf cfg.enable {
-    environment.systemPackages = with pkgs; [ mosh ];
-    networking.firewall.allowedUDPPortRanges = [ { from = 60000; to = 61000; } ];
-    security.wrappers = mkIf cfg.withUtempter {
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.mosh ];
+    networking.firewall.allowedUDPPortRanges = lib.optional cfg.openFirewall { from = 60000; to = 61000; };
+    security.wrappers = lib.mkIf cfg.withUtempter {
       utempter = {
         source = "${pkgs.libutempter}/lib/utempter/utempter";
         owner = "root";
diff --git a/nixos/modules/programs/mtr.nix b/nixos/modules/programs/mtr.nix
index 173f247294174..e247d645b8618 100644
--- a/nixos/modules/programs/mtr.nix
+++ b/nixos/modules/programs/mtr.nix
@@ -17,14 +17,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mtr;
-        defaultText = literalExpression "pkgs.mtr";
-        description = lib.mdDoc ''
-          The package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "mtr" { };
     };
   };
 
diff --git a/nixos/modules/programs/nano.nix b/nixos/modules/programs/nano.nix
index 7705bf0ddc72c..461681b598631 100644
--- a/nixos/modules/programs/nano.nix
+++ b/nixos/modules/programs/nano.nix
@@ -2,14 +2,16 @@
 
 let
   cfg = config.programs.nano;
-  LF = "\n";
 in
 
 {
-  ###### interface
-
   options = {
     programs.nano = {
+      enable = lib.mkEnableOption (lib.mdDoc "nano") // {
+        default = true;
+      };
+
+      package = lib.mkPackageOption pkgs "nano" { };
 
       nanorc = lib.mkOption {
         type = lib.types.lines;
@@ -24,6 +26,7 @@ in
           set tabsize 2
         '';
       };
+
       syntaxHighlight = lib.mkOption {
         type = lib.types.bool;
         default = true;
@@ -32,20 +35,14 @@ in
     };
   };
 
-  ###### implementation
-
-  config = lib.mkIf (cfg.nanorc != "" || cfg.syntaxHighlight) {
-    environment.etc.nanorc.text = lib.concatStringsSep LF (
-      ( lib.optionals cfg.syntaxHighlight [
-          "# The line below is added because value of programs.nano.syntaxHighlight is set to true"
-          ''include "${pkgs.nano}/share/nano/*.nanorc"''
-          ""
-      ])
-      ++ ( lib.optionals (cfg.nanorc != "") [
-        "# The lines below have been set from value of programs.nano.nanorc"
-        cfg.nanorc
-      ])
-    );
+  config = lib.mkIf cfg.enable {
+    environment = {
+      etc.nanorc.text = (lib.optionalString cfg.syntaxHighlight ''
+        # load syntax highlighting files
+        include "${cfg.package}/share/nano/*.nanorc"
+        include "${cfg.package}/share/nano/extra/*.nanorc"
+      '') + cfg.nanorc;
+      systemPackages = [ cfg.package ];
+    };
   };
-
 }
diff --git a/nixos/modules/programs/neovim.nix b/nixos/modules/programs/neovim.nix
index 1b53b9b5d9194..77abec7ef7e90 100644
--- a/nixos/modules/programs/neovim.nix
+++ b/nixos/modules/programs/neovim.nix
@@ -86,12 +86,7 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.neovim-unwrapped;
-      defaultText = literalExpression "pkgs.neovim-unwrapped";
-      description = lib.mdDoc "The package to use for the neovim binary.";
-    };
+    package = mkPackageOption pkgs "neovim-unwrapped" { };
 
     finalPackage = mkOption {
       type = types.package;
diff --git a/nixos/modules/programs/nexttrace.nix b/nixos/modules/programs/nexttrace.nix
index 091d4f17f9f61..09143c5f861db 100644
--- a/nixos/modules/programs/nexttrace.nix
+++ b/nixos/modules/programs/nexttrace.nix
@@ -8,7 +8,7 @@ in
   options = {
     programs.nexttrace = {
       enable = lib.mkEnableOption (lib.mdDoc "Nexttrace to the global environment and configure a setcap wrapper for it");
-      package = lib.mkPackageOptionMD pkgs "nexttrace" { };
+      package = lib.mkPackageOption pkgs "nexttrace" { };
     };
   };
 
diff --git a/nixos/modules/programs/nix-index.nix b/nixos/modules/programs/nix-index.nix
index a494b9d8c2c9b..f3e7d22737fa9 100644
--- a/nixos/modules/programs/nix-index.nix
+++ b/nixos/modules/programs/nix-index.nix
@@ -5,12 +5,7 @@ in {
   options.programs.nix-index = with lib; {
     enable = mkEnableOption (lib.mdDoc "nix-index, a file database for nixpkgs");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.nix-index;
-      defaultText = literalExpression "pkgs.nix-index";
-      description = lib.mdDoc "Package providing the `nix-index` tool.";
-    };
+    package = mkPackageOption pkgs "nix-index" { };
 
     enableBashIntegration = mkEnableOption (lib.mdDoc "Bash integration") // {
       default = true;
diff --git a/nixos/modules/programs/nix-ld.nix b/nixos/modules/programs/nix-ld.nix
index d54b3917f89a8..6f36ce33640cd 100644
--- a/nixos/modules/programs/nix-ld.nix
+++ b/nixos/modules/programs/nix-ld.nix
@@ -37,7 +37,7 @@ in
   meta.maintainers = [ lib.maintainers.mic92 ];
   options.programs.nix-ld = {
     enable = lib.mkEnableOption (lib.mdDoc ''nix-ld, Documentation: <https://github.com/Mic92/nix-ld>'');
-    package = lib.mkPackageOptionMD pkgs "nix-ld" { };
+    package = lib.mkPackageOption 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.";
@@ -47,7 +47,7 @@ in
   };
 
   config = lib.mkIf config.programs.nix-ld.enable {
-    systemd.tmpfiles.packages = [ cfg.package ];
+    environment.ldso = "${cfg.package}/libexec/nix-ld";
 
     environment.systemPackages = [ nix-ld-libraries ];
 
diff --git a/nixos/modules/programs/nncp.nix b/nixos/modules/programs/nncp.nix
index 98fea84ab7407..e078b718410c5 100644
--- a/nixos/modules/programs/nncp.nix
+++ b/nixos/modules/programs/nncp.nix
@@ -23,12 +23,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.nncp;
-      defaultText = literalExpression "pkgs.nncp";
-      description = lib.mdDoc "The NNCP package to use system-wide.";
-    };
+    package = mkPackageOption pkgs "nncp" { };
 
     secrets = mkOption {
       type = with types; listOf str;
diff --git a/nixos/modules/programs/noisetorch.nix b/nixos/modules/programs/noisetorch.nix
index c022b01d79af9..d8135877d02f2 100644
--- a/nixos/modules/programs/noisetorch.nix
+++ b/nixos/modules/programs/noisetorch.nix
@@ -8,14 +8,7 @@ in
   options.programs.noisetorch = {
     enable = mkEnableOption (lib.mdDoc "noisetorch + setcap wrapper");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.noisetorch;
-      defaultText = literalExpression "pkgs.noisetorch";
-      description = lib.mdDoc ''
-        The noisetorch package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "noisetorch" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/programs/npm.nix b/nixos/modules/programs/npm.nix
index 48dc48e668f33..8113ea1ba4ea6 100644
--- a/nixos/modules/programs/npm.nix
+++ b/nixos/modules/programs/npm.nix
@@ -13,12 +13,8 @@ in
     programs.npm = {
       enable = mkEnableOption (lib.mdDoc "{command}`npm` global config");
 
-      package = mkOption {
-        type = types.package;
-        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";
+      package = mkPackageOption pkgs [ "nodePackages" "npm" ] {
+        example = "nodePackages_13_x.npm";
       };
 
       npmrc = mkOption {
@@ -34,7 +30,7 @@ in
           prefix = ''${HOME}/.npm
           https-proxy=proxy.example.com
           init-license=MIT
-          init-author-url=http://npmjs.org
+          init-author-url=https://www.npmjs.com/
           color=true
         '';
       };
diff --git a/nixos/modules/programs/oddjobd.nix b/nixos/modules/programs/oddjobd.nix
index b0920d007c9eb..08bb8b2684731 100644
--- a/nixos/modules/programs/oddjobd.nix
+++ b/nixos/modules/programs/oddjobd.nix
@@ -10,11 +10,6 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    assertions = [
-      { assertion = false;
-        message = "The oddjob service was found to be broken without NixOS test or maintainer. Please take ownership of this service.";
-      }
-    ];
     systemd.packages = [ cfg.package ];
 
     systemd.services.oddjobd = {
@@ -30,4 +25,6 @@ in
       };
     };
   };
+
+  meta.maintainers = with lib.maintainers; [ SohamG ];
 }
diff --git a/nixos/modules/programs/openvpn3.nix b/nixos/modules/programs/openvpn3.nix
index df7e9ef22c105..37a1bfeb0c3e6 100644
--- a/nixos/modules/programs/openvpn3.nix
+++ b/nixos/modules/programs/openvpn3.nix
@@ -8,11 +8,23 @@ in
 {
   options.programs.openvpn3 = {
     enable = mkEnableOption (lib.mdDoc "the openvpn3 client");
+    package = mkOption {
+      type = types.package;
+      default = pkgs.openvpn3.override {
+        enableSystemdResolved = config.services.resolved.enable;
+      };
+      defaultText = literalExpression ''pkgs.openvpn3.override {
+        enableSystemdResolved = config.services.resolved.enable;
+      }'';
+      description = lib.mdDoc ''
+        Which package to use for `openvpn3`.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
-    services.dbus.packages = with pkgs; [
-      openvpn3
+    services.dbus.packages = [
+      cfg.package
     ];
 
     users.users.openvpn = {
@@ -25,8 +37,8 @@ in
       gid = config.ids.gids.openvpn;
     };
 
-    environment.systemPackages = with pkgs; [
-      openvpn3
+    environment.systemPackages = [
+      cfg.package
     ];
   };
 
diff --git a/nixos/modules/programs/partition-manager.nix b/nixos/modules/programs/partition-manager.nix
index c18598b7c25d6..cf0491ff028fc 100644
--- a/nixos/modules/programs/partition-manager.nix
+++ b/nixos/modules/programs/partition-manager.nix
@@ -14,6 +14,6 @@ with lib;
   config = mkIf config.programs.partition-manager.enable {
     services.dbus.packages = [ pkgs.libsForQt5.kpmcore ];
     # `kpmcore` need to be installed to pull in polkit actions.
-    environment.systemPackages = [ pkgs.libsForQt5.kpmcore pkgs.partition-manager ];
+    environment.systemPackages = [ pkgs.libsForQt5.kpmcore pkgs.libsForQt5.partitionmanager ];
   };
 }
diff --git a/nixos/modules/programs/projecteur.nix b/nixos/modules/programs/projecteur.nix
new file mode 100644
index 0000000000000..140de0209e680
--- /dev/null
+++ b/nixos/modules/programs/projecteur.nix
@@ -0,0 +1,20 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.projecteur;
+in
+{
+  options.programs.projecteur = {
+    enable = lib.mkEnableOption (lib.mdDoc "projecteur");
+    package = lib.mkPackageOption pkgs "projecteur" { };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ benneti drupol ];
+  };
+}
diff --git a/nixos/modules/programs/proxychains.nix b/nixos/modules/programs/proxychains.nix
index 9bdd5d405668e..acd41f3552448 100644
--- a/nixos/modules/programs/proxychains.nix
+++ b/nixos/modules/programs/proxychains.nix
@@ -51,8 +51,8 @@ in {
 
       enable = mkEnableOption (lib.mdDoc "installing proxychains configuration");
 
-      package = mkPackageOptionMD pkgs "proxychains" {
-        example = "pkgs.proxychains-ng";
+      package = mkPackageOption pkgs "proxychains" {
+        example = "proxychains-ng";
       };
 
       chain = {
diff --git a/nixos/modules/programs/qdmr.nix b/nixos/modules/programs/qdmr.nix
index 1bb81317bda8d..03ad4d008873c 100644
--- a/nixos/modules/programs/qdmr.nix
+++ b/nixos/modules/programs/qdmr.nix
@@ -13,7 +13,7 @@ in {
   options = {
     programs.qdmr = {
       enable = lib.mkEnableOption (lib.mdDoc "QDMR - a GUI application and command line tool for programming DMR radios");
-      package = lib.mkPackageOptionMD pkgs "qdmr" { };
+      package = lib.mkPackageOption pkgs "qdmr" { };
     };
   };
 
diff --git a/nixos/modules/programs/regreet.nix b/nixos/modules/programs/regreet.nix
index f6c750a45bf54..0c44d717044ec 100644
--- a/nixos/modules/programs/regreet.nix
+++ b/nixos/modules/programs/regreet.nix
@@ -24,7 +24,7 @@ in
       '';
     };
 
-    package = lib.mkPackageOptionMD pkgs [ "greetd" "regreet" ] { };
+    package = lib.mkPackageOption pkgs [ "greetd" "regreet" ] { };
 
     settings = lib.mkOption {
       type = lib.types.either lib.types.path settingsFormat.type;
@@ -36,6 +36,19 @@ in
       '';
     };
 
+    cageArgs = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ "-s" ];
+      example = lib.literalExpression
+        ''
+          [ "-s" "-m" "last" ]
+        '';
+      description = lib.mdDoc ''
+        Additional arguments to be passed to
+        [cage](https://github.com/cage-kiosk/cage).
+      '';
+    };
+
     extraCss = lib.mkOption {
       type = lib.types.either lib.types.path lib.types.lines;
       default = "";
@@ -50,7 +63,7 @@ in
   config = lib.mkIf cfg.enable {
     services.greetd = {
       enable = lib.mkDefault true;
-      settings.default_session.command = lib.mkDefault "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -- ${lib.getExe cfg.package}";
+      settings.default_session.command = lib.mkDefault "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package}";
     };
 
     environment.etc = {
@@ -66,10 +79,10 @@ in
     };
 
     systemd.tmpfiles.rules = let
-      user = config.services.greetd.settings.default_session.user;
+      group = config.users.users.${config.services.greetd.settings.default_session.user}.group;
     in [
-      "d /var/log/regreet 0755 greeter ${user} - -"
-      "d /var/cache/regreet 0755 greeter ${user} - -"
+      "d /var/log/regreet 0755 greeter ${group} - -"
+      "d /var/cache/regreet 0755 greeter ${group} - -"
     ];
   };
 }
diff --git a/nixos/modules/programs/rust-motd.nix b/nixos/modules/programs/rust-motd.nix
index d5f1820ba7523..4c9b1018596b7 100644
--- a/nixos/modules/programs/rust-motd.nix
+++ b/nixos/modules/programs/rust-motd.nix
@@ -5,6 +5,23 @@ with lib;
 let
   cfg = config.programs.rust-motd;
   format = pkgs.formats.toml { };
+
+  # Order the sections in the TOML according to the order of sections
+  # in `cfg.order`.
+  motdConf = pkgs.runCommand "motd.conf"
+    {
+      __structuredAttrs = true;
+      inherit (cfg) order settings;
+      nativeBuildInputs = [ pkgs.remarshal pkgs.jq ];
+    }
+    ''
+      cat "$NIX_ATTRS_JSON_FILE" \
+        | jq '.settings as $settings
+              | .order
+              | map({ key: ., value: $settings."\(.)" })
+              | from_entries' -r \
+        | json2toml /dev/stdin "$out"
+    '';
 in {
   options.programs.rust-motd = {
     enable = mkEnableOption (lib.mdDoc "rust-motd");
@@ -27,10 +44,43 @@ in {
         For possible formats, please refer to {manpage}`systemd.time(7)`.
       '';
     };
+    order = mkOption {
+      type = types.listOf types.str;
+      default = attrNames cfg.settings;
+      defaultText = literalExpression "attrNames cfg.settings";
+      description = mdDoc ''
+        The order of the sections in [](#opt-programs.rust-motd.settings).
+        By default they are ordered alphabetically.
+
+        Context: since attribute sets in Nix are always
+        ordered alphabetically internally this means that
+
+        ```nix
+        {
+          uptime = { /* ... */ };
+          banner = { /* ... */ };
+        }
+        ```
+
+        will still have `banner` displayed before `uptime`.
+
+        To work around that, this option can be used to define the order of all keys,
+        i.e.
+
+        ```nix
+        {
+          order = [
+            "uptime"
+            "banner"
+          ];
+        }
+        ```
+
+        makes sure that `uptime` is placed before `banner` in the motd.
+      '';
+    };
     settings = mkOption {
-      type = types.submodule {
-        freeformType = format.type;
-      };
+      type = types.attrsOf 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)
@@ -45,14 +95,21 @@ in {
           `programs.rust-motd` is incompatible with `users.motd`!
         '';
       }
+      { assertion = sort (a: b: a < b) cfg.order == attrNames cfg.settings;
+        message = ''
+          Please ensure that every section from `programs.rust-motd.settings` is present in
+          `programs.rust-motd.order`.
+        '';
+      }
     ];
     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";
+      wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         ExecStart = "${pkgs.writeShellScript "update-motd" ''
-          ${pkgs.rust-motd}/bin/rust-motd ${format.generate "motd.conf" cfg.settings} > motd
+          ${pkgs.rust-motd}/bin/rust-motd ${motdConf} > motd
         ''}";
         CapabilityBoundingSet = [ "" ];
         LockPersonality = true;
diff --git a/nixos/modules/programs/screen.nix b/nixos/modules/programs/screen.nix
index 68de9e52d7be3..41bfb5d7809af 100644
--- a/nixos/modules/programs/screen.nix
+++ b/nixos/modules/programs/screen.nix
@@ -1,33 +1,41 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkOption mkIf types;
   cfg = config.programs.screen;
 in
 
 {
-  ###### interface
-
   options = {
     programs.screen = {
+      enable = lib.mkEnableOption (lib.mdDoc "screen, a basic terminal multiplexer");
+
+      package = lib.mkPackageOptionMD pkgs "screen" { };
 
-      screenrc = mkOption {
-        default = "";
-        description = lib.mdDoc ''
-          The contents of /etc/screenrc file.
+      screenrc = lib.mkOption {
+        type = with lib.types; nullOr lines;
+        example = ''
+          defscrollback 10000
+          startup_message off
         '';
-        type = types.lines;
+        description = lib.mdDoc "The contents of {file}`/etc/screenrc` file";
       };
     };
   };
 
-  ###### implementation
-
-  config = mkIf (cfg.screenrc != "") {
-    environment.etc.screenrc.text = cfg.screenrc;
-
-    environment.systemPackages = [ pkgs.screen ];
+  config = {
+    # TODO: Added in 24.05, remove before 24.11
+    assertions = [
+      {
+        assertion = cfg.screenrc != null -> cfg.enable;
+        message = "`programs.screen.screenrc` has been configured, but `programs.screen.enable` is not true";
+      }
+    ];
+  } // lib.mkIf cfg.enable {
+    environment.etc.screenrc = {
+      enable = cfg.screenrc != null;
+      text = cfg.screenrc;
+    };
+    environment.systemPackages = [ cfg.package ];
     security.pam.services.screen = {};
   };
-
 }
diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix
index 00895db03fc32..b232767385c59 100644
--- a/nixos/modules/programs/shadow.nix
+++ b/nixos/modules/programs/shadow.nix
@@ -7,7 +7,7 @@ in
 {
   options = with types; {
     security.loginDefs = {
-      package = mkPackageOptionMD pkgs "shadow" { };
+      package = mkPackageOption pkgs "shadow" { };
 
       chfnRestrict = mkOption {
         description = mdDoc ''
diff --git a/nixos/modules/programs/singularity.nix b/nixos/modules/programs/singularity.nix
index 05fdb4842c543..7f285ab05537a 100644
--- a/nixos/modules/programs/singularity.nix
+++ b/nixos/modules/programs/singularity.nix
@@ -12,14 +12,8 @@ in
         Whether to install Singularity/Apptainer with system-level overriding such as SUID support.
       '';
     };
-    package = mkOption {
-      type = types.package;
-      default = pkgs.singularity;
-      defaultText = literalExpression "pkgs.singularity";
-      example = literalExpression "pkgs.apptainer";
-      description = mdDoc ''
-        Singularity/Apptainer package to override and install.
-      '';
+    package = mkPackageOption pkgs "singularity" {
+      example = "apptainer";
     };
     packageOverriden = mkOption {
       type = types.nullOr types.package;
@@ -45,6 +39,18 @@ in
         Use `lib.mkForce` to forcefully specify the overridden package.
       '';
     };
+    enableExternalLocalStateDir = mkOption {
+      type = types.bool;
+      default = true;
+      example = false;
+      description = mdDoc ''
+        Whether to use top-level directories as LOCALSTATEDIR
+        instead of the store path ones.
+        This affects the SESSIONDIR of Apptainer/Singularity.
+        If set to true, the SESSIONDIR will become
+        `/var/lib/''${projectName}/mnt/session`.
+      '';
+    };
     enableFakeroot = mkOption {
       type = types.bool;
       default = true;
@@ -55,7 +61,12 @@ in
     };
     enableSuid = mkOption {
       type = types.bool;
-      default = true;
+      # SingularityCE requires SETUID for most things. Apptainer prefers user
+      # namespaces, e.g. `apptainer exec --nv` would fail if built
+      # `--with-suid`:
+      # > `FATAL: nvidia-container-cli not allowed in setuid mode`
+      default = cfg.package.projectName != "apptainer";
+      defaultText = literalExpression ''config.services.singularity.package.projectName != "apptainer"'';
       example = false;
       description = mdDoc ''
         Whether to enable the SUID support of Singularity/Apptainer.
@@ -65,7 +76,9 @@ in
 
   config = mkIf cfg.enable {
     programs.singularity.packageOverriden = (cfg.package.override (
-      optionalAttrs cfg.enableFakeroot {
+      optionalAttrs cfg.enableExternalLocalStateDir {
+        externalLocalStateDir = "/var/lib";
+      } // optionalAttrs cfg.enableFakeroot {
         newuidmapPath = "/run/wrappers/bin/newuidmap";
         newgidmapPath = "/run/wrappers/bin/newgidmap";
       } // optionalAttrs cfg.enableSuid {
@@ -80,12 +93,8 @@ in
       group = "root";
       source = "${cfg.packageOverriden}/libexec/${cfg.packageOverriden.projectName}/bin/starter-suid.orig";
     };
-    systemd.tmpfiles.rules = [
+    systemd.tmpfiles.rules = mkIf cfg.enableExternalLocalStateDir [
       "d /var/lib/${cfg.packageOverriden.projectName}/mnt/session 0770 root root -"
-      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/final 0770 root root -"
-      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/overlay 0770 root root -"
-      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/container 0770 root root -"
-      "d /var/lib/${cfg.packageOverriden.projectName}/mnt/source 0770 root root -"
     ];
   };
 
diff --git a/nixos/modules/programs/skim.nix b/nixos/modules/programs/skim.nix
index 8dadf322606e2..57a5d68ec3d5a 100644
--- a/nixos/modules/programs/skim.nix
+++ b/nixos/modules/programs/skim.nix
@@ -1,6 +1,6 @@
 { pkgs, config, lib, ... }:
 let
-  inherit (lib) mdDoc mkEnableOption mkPackageOptionMD optional optionalString;
+  inherit (lib) mdDoc mkEnableOption mkPackageOption optional optionalString;
   cfg = config.programs.skim;
 in
 {
@@ -8,7 +8,7 @@ in
     programs.skim = {
       fuzzyCompletion = mkEnableOption (mdDoc "fuzzy completion with skim");
       keybindings = mkEnableOption (mdDoc "skim keybindings");
-      package = mkPackageOptionMD pkgs "skim" {};
+      package = mkPackageOption pkgs "skim" {};
     };
   };
 
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 7c85d1e7c3d51..0c1461709c22b 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -8,14 +8,13 @@ let
 
   cfg  = config.programs.ssh;
 
-  askPassword = cfg.askPassword;
-
   askPasswordWrapper = pkgs.writeScript "ssh-askpass-wrapper"
     ''
       #! ${pkgs.runtimeShell} -e
       export DISPLAY="$(systemctl --user show-environment | ${pkgs.gnused}/bin/sed 's/^DISPLAY=\(.*\)/\1/; t; d')"
+      export XAUTHORITY="$(systemctl --user show-environment | ${pkgs.gnused}/bin/sed 's/^XAUTHORITY=\(.*\)/\1/; t; d')"
       export WAYLAND_DISPLAY="$(systemctl --user show-environment | ${pkgs.gnused}/bin/sed 's/^WAYLAND_DISPLAY=\(.*\)/\1/; t; d')"
-      exec ${askPassword} "$@"
+      exec ${cfg.askPassword} "$@"
     '';
 
   knownHosts = attrValues cfg.knownHosts;
@@ -52,10 +51,11 @@ in
       };
 
       forwardX11 = mkOption {
-        type = types.bool;
+        type = with lib.types; nullOr bool;
         default = false;
         description = lib.mdDoc ''
           Whether to request X11 forwarding on outgoing connections by default.
+          If set to null, the option is not set at all.
           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.
           Note: there are some security risks to forwarding an X11 connection.
@@ -132,14 +132,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.openssh;
-        defaultText = literalExpression "pkgs.openssh";
-        description = lib.mdDoc ''
-          The package used for the openssh client and daemon.
-        '';
-      };
+      package = mkPackageOption pkgs "openssh" { };
 
       knownHosts = mkOption {
         default = {};
@@ -281,10 +274,10 @@ in
   config = {
 
     programs.ssh.setXAuthLocation =
-      mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 || config.services.openssh.settings.X11Forwarding);
+      mkDefault (config.services.xserver.enable || config.programs.ssh.forwardX11 == true || config.services.openssh.settings.X11Forwarding);
 
     assertions =
-      [ { assertion = cfg.forwardX11 -> cfg.setXAuthLocation;
+      [ { assertion = cfg.forwardX11 == true -> cfg.setXAuthLocation;
           message = "cannot enable X11 forwarding without setting XAuth location";
         }
       ] ++ flip mapAttrsToList cfg.knownHosts (name: data: {
@@ -305,11 +298,8 @@ in
         AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
         GlobalKnownHostsFile ${concatStringsSep " " knownHostsFiles}
 
-        ${optionalString cfg.setXAuthLocation ''
-          XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
-        ''}
-
-        ForwardX11 ${if cfg.forwardX11 then "yes" else "no"}
+        ${optionalString cfg.setXAuthLocation "XAuthLocation ${pkgs.xorg.xauth}/bin/xauth"}
+        ${lib.optionalString (cfg.forwardX11 != null) "ForwardX11 ${if cfg.forwardX11 then "yes" else "no"}"}
 
         ${optionalString (cfg.pubkeyAcceptedKeyTypes != []) "PubkeyAcceptedKeyTypes ${concatStringsSep "," cfg.pubkeyAcceptedKeyTypes}"}
         ${optionalString (cfg.hostKeyAlgorithms != []) "HostKeyAlgorithms ${concatStringsSep "," cfg.hostKeyAlgorithms}"}
@@ -351,7 +341,7 @@ in
         fi
       '';
 
-    environment.variables.SSH_ASKPASS = optionalString cfg.enableAskPassword askPassword;
+    environment.variables.SSH_ASKPASS = optionalString cfg.enableAskPassword cfg.askPassword;
 
   };
 }
diff --git a/nixos/modules/programs/starship.nix b/nixos/modules/programs/starship.nix
index 9dca39da5edc0..34f6f0882c617 100644
--- a/nixos/modules/programs/starship.nix
+++ b/nixos/modules/programs/starship.nix
@@ -1,13 +1,21 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.programs.starship;
 
   settingsFormat = pkgs.formats.toml { };
 
-  settingsFile = settingsFormat.generate "starship.toml" cfg.settings;
+  userSettingsFile = settingsFormat.generate "starship.toml" cfg.settings;
+
+  settingsFile = if cfg.presets == [] then userSettingsFile else pkgs.runCommand "starship.toml"
+    {
+      nativeBuildInputs = [ pkgs.yq ];
+    } ''
+    tomlq -s -t 'reduce .[] as $item ({}; . * $item)' \
+      ${lib.concatStringsSep " " (map (f: "${pkgs.starship}/share/starship/presets/${f}.toml") cfg.presets)} \
+      ${userSettingsFile} \
+      > $out
+  '';
 
   initOption =
     if cfg.interactiveOnly then
@@ -18,19 +26,28 @@ let
 in
 {
   options.programs.starship = {
-    enable = mkEnableOption (lib.mdDoc "the Starship shell prompt");
+    enable = lib.mkEnableOption (lib.mdDoc "the Starship shell prompt");
 
-    interactiveOnly = mkOption {
+    interactiveOnly = lib.mkOption {
       default = true;
       example = false;
-      type = types.bool;
+      type = lib.types.bool;
       description = lib.mdDoc ''
         Whether to enable starship only when the shell is interactive.
         Some plugins require this to be set to false to function correctly.
       '';
     };
 
-    settings = mkOption {
+    presets = lib.mkOption {
+      default = [ ];
+      example = [ "nerd-font-symbols" ];
+      type = with lib.types; listOf str;
+      description = lib.mdDoc ''
+        Presets files to be merged with settings in order.
+      '';
+    };
+
+    settings = lib.mkOption {
       inherit (settingsFormat) type;
       default = { };
       description = lib.mdDoc ''
@@ -41,24 +58,42 @@ in
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     programs.bash.${initOption} = ''
       if [[ $TERM != "dumb" ]]; then
-        export STARSHIP_CONFIG=${settingsFile}
+        # don't set STARSHIP_CONFIG automatically if there's a user-specified
+        # config file.  starship appears to use a hardcoded config location
+        # rather than one inside an XDG folder:
+        # https://github.com/starship/starship/blob/686bda1706e5b409129e6694639477a0f8a3f01b/src/configure.rs#L651
+        if [[ ! -f "$HOME/.config/starship.toml" ]]; then
+          export STARSHIP_CONFIG=${settingsFile}
+        fi
         eval "$(${pkgs.starship}/bin/starship init bash)"
       fi
     '';
 
     programs.fish.${initOption} = ''
       if test "$TERM" != "dumb"
-        set -x STARSHIP_CONFIG ${settingsFile}
+        # don't set STARSHIP_CONFIG automatically if there's a user-specified
+        # config file.  starship appears to use a hardcoded config location
+        # rather than one inside an XDG folder:
+        # https://github.com/starship/starship/blob/686bda1706e5b409129e6694639477a0f8a3f01b/src/configure.rs#L651
+        if not test -f "$HOME/.config/starship.toml";
+          set -x STARSHIP_CONFIG ${settingsFile}
+        end
         eval (${pkgs.starship}/bin/starship init fish)
       end
     '';
 
     programs.zsh.${initOption} = ''
       if [[ $TERM != "dumb" ]]; then
-        export STARSHIP_CONFIG=${settingsFile}
+        # don't set STARSHIP_CONFIG automatically if there's a user-specified
+        # config file.  starship appears to use a hardcoded config location
+        # rather than one inside an XDG folder:
+        # https://github.com/starship/starship/blob/686bda1706e5b409129e6694639477a0f8a3f01b/src/configure.rs#L651
+        if [[ ! -f "$HOME/.config/starship.toml" ]]; then
+          export STARSHIP_CONFIG=${settingsFile}
+        fi
         eval "$(${pkgs.starship}/bin/starship init zsh)"
       fi
     '';
diff --git a/nixos/modules/programs/streamdeck-ui.nix b/nixos/modules/programs/streamdeck-ui.nix
index 4c055029e39b9..47b1681cd6344 100644
--- a/nixos/modules/programs/streamdeck-ui.nix
+++ b/nixos/modules/programs/streamdeck-ui.nix
@@ -15,7 +15,7 @@ in
       description = lib.mdDoc "Whether streamdeck-ui should be started automatically.";
     };
 
-    package = mkPackageOptionMD pkgs "streamdeck-ui" {
+    package = mkPackageOption pkgs "streamdeck-ui" {
       default = [ "streamdeck-ui" ];
     };
 
@@ -24,7 +24,7 @@ in
   config = mkIf cfg.enable {
     environment.systemPackages = with pkgs; [
       cfg.package
-      (mkIf cfg.autoStart (makeAutostartItem { name = "streamdeck-ui"; package = cfg.package; }))
+      (mkIf cfg.autoStart (makeAutostartItem { name = "streamdeck-ui-noui"; package = cfg.package; }))
     ];
 
     services.udev.packages = [ cfg.package ];
diff --git a/nixos/modules/programs/tsm-client.nix b/nixos/modules/programs/tsm-client.nix
index 41560544c2c70..45d436221ee38 100644
--- a/nixos/modules/programs/tsm-client.nix
+++ b/nixos/modules/programs/tsm-client.nix
@@ -1,193 +1,144 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, options, pkgs, ... }:  # XXX migration code for freeform settings: `options` can be removed in 2025
+let optionsGlobal = options; in
 
 let
 
-  inherit (builtins) length map;
-  inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs;
+  inherit (lib.attrsets) attrNames attrValues mapAttrsToList removeAttrs;
+  inherit (lib.lists) all allUnique concatLists elem isList map;
   inherit (lib.modules) mkDefault mkIf;
-  inherit (lib.options) literalExpression mkEnableOption mkOption;
-  inherit (lib.strings) concatLines optionalString toLower;
-  inherit (lib.types) addCheck attrsOf lines nonEmptyStr nullOr package path port str strMatching submodule;
+  inherit (lib.options) mkEnableOption mkOption mkPackageOption;
+  inherit (lib.strings) concatLines match optionalString toLower;
+  inherit (lib.trivial) isInt;
+  inherit (lib.types) addCheck attrsOf coercedTo either enum int lines listOf nonEmptyStr nullOr oneOf path port singleLineStr strMatching submodule;
 
-  # Checks if given list of strings contains unique
-  # elements when compared without considering case.
-  # Type: checkIUnique :: [string] -> bool
-  # Example: checkIUnique ["foo" "Foo"] => false
-  checkIUnique = lst:
-    let
-      lenUniq = l: length (lib.lists.unique l);
-    in
-      lenUniq lst == lenUniq (map toLower lst);
+  scalarType =
+    # see the option's description below for the
+    # handling/transformation of each possible type
+    oneOf [ (enum [ true null ]) int path singleLineStr ];
 
   # TSM rejects servername strings longer than 64 chars.
-  servernameType = strMatching ".{1,64}";
+  servernameType = strMatching "[^[:space:]]{1,64}";
 
   serverOptions = { name, config, ... }: {
-    options.name = mkOption {
+    freeformType = attrsOf (either scalarType (listOf scalarType));
+    # Client system-options file directives are explained here:
+    # https://www.ibm.com/docs/en/storage-protect/8.1.20?topic=commands-processing-options
+    options.servername = mkOption {
       type = servernameType;
+      default = name;
       example = "mainTsmServer";
       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
-        `server`
-        directive in {file}`dsm.sys`.
+        must not contain space or more than 64 chars.
       '';
     };
-    options.server = mkOption {
+    options.tcpserveraddress = mkOption {
       type = nonEmptyStr;
       example = "tsmserver.company.com";
       description = lib.mdDoc ''
         Host/domain name or IP address of the IBM TSM server.
-        The value will be used for the
-        `tcpserveraddress`
-        directive in {file}`dsm.sys`.
       '';
     };
-    options.port = mkOption {
+    options.tcpport = mkOption {
       type = addCheck port (p: p<=32767);
       default = 1500;  # official default
       description = lib.mdDoc ''
         TCP port of the IBM TSM server.
-        The value will be used for the
-        `tcpport`
-        directive in {file}`dsm.sys`.
         TSM does not support ports above 32767.
       '';
     };
-    options.node = mkOption {
+    options.nodename = mkOption {
       type = nonEmptyStr;
       example = "MY-TSM-NODE";
       description = lib.mdDoc ''
         Target node name on the IBM TSM server.
-        The value will be used for the
-        `nodename`
-        directive in {file}`dsm.sys`.
       '';
     };
     options.genPasswd = mkEnableOption (lib.mdDoc ''
       automatic client password generation.
-      This option influences the
-      `passwordaccess`
-      directive in {file}`dsm.sys`.
+      This option does *not* cause a line in
+      {file}`dsm.sys` by itself, but generates a
+      corresponding `passwordaccess` directive.
       The password will be stored in the directory
-      given by the option {option}`passwdDir`.
+      given by the option {option}`passworddir`.
       *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;
+    options.passwordaccess = mkOption {
+      type = enum [ "generate" "prompt" ];
+      visible = false;
+    };
+    options.passworddir = mkOption {
+      type = nullOr path;
+      default = null;
       example = "/home/alice/tsm-password";
       description = lib.mdDoc ''
         Directory that holds the TSM
         node's password information.
-        The value will be used for the
-        `passworddir`
-        directive in {file}`dsm.sys`.
       '';
     };
-    options.includeExclude = mkOption {
-      type = lines;
-      default = "";
+    options.inclexcl = mkOption {
+      type = coercedTo lines
+        (pkgs.writeText "inclexcl.dsm.sys")
+        (nullOr path);
+      default = null;
       example = ''
         exclude.dir     /nix/store
         include.encrypt /home/.../*
       '';
       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
-        `inclexcl`
-        directive in {file}`dsm.sys` points to.
-      '';
-    };
-    options.extraConfig = mkOption {
-      # TSM option keys are case insensitive;
-      # we have to ensure there are no keys that
-      # differ only by upper and lower case.
-      type = addCheck
-        (attrsOf (nullOr str))
-        (attrs: checkIUnique (attrNames attrs));
-      default = {};
-      example.compression = "yes";
-      example.passwordaccess = null;
-      description = lib.mdDoc ''
-        Additional key-value pairs for the server stanza.
-        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).
-      '';
-    };
-    options.text = mkOption {
-      type = lines;
-      example = literalExpression
-        ''lib.modules.mkAfter "compression no"'';
-      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 can't
-        control the order of lines in the resulting stanza.
-        Note that the `server`
-        line at the beginning of the stanza is
-        not part of this option's value.
+        Text lines with `include.*` and `exclude.*` directives
+        to be used when sending files to the IBM TSM server,
+        or an absolute path pointing to a file with such lines.
       '';
     };
-    options.stanza = mkOption {
-      type = str;
-      internal = true;
-      visible = false;
-      description = lib.mdDoc "Server stanza text generated from the options.";
-    };
-    config.name = mkDefault name;
-    # Client system-options file directives are explained here:
-    # https://www.ibm.com/docs/en/spectrum-protect/8.1.13?topic=commands-processing-options
-    config.extraConfig =
-      mapAttrs (lib.trivial.const mkDefault) (
-        {
-          commmethod = "v6tcpip";  # uses v4 or v6, based on dns lookup result
-          tcpserveraddress = config.server;
-          tcpport = builtins.toString config.port;
-          nodename = config.node;
-          passwordaccess = if config.genPasswd then "generate" else "prompt";
-          passworddir = ''"${config.passwdDir}"'';
-        } // optionalAttrs (config.includeExclude!="") {
-          inclexcl = ''"${pkgs.writeText "inclexcl.dsm.sys" config.includeExclude}"'';
-        }
-      );
-    config.text =
-      let
-        attrset = filterAttrs (k: v: v!=null) config.extraConfig;
-        mkLine = k: v: k + optionalString (v!="") "  ${v}";
-        lines = mapAttrsToList mkLine attrset;
-      in
-        concatLines lines;
-    config.stanza = ''
-      server  ${config.name}
-      ${config.text}
-    '';
+    config.commmethod = mkDefault "v6tcpip";  # uses v4 or v6, based on dns lookup result
+    config.passwordaccess = if config.genPasswd then "generate" else "prompt";
+    # XXX migration code for freeform settings, these can be removed in 2025:
+    options.warnings = optionsGlobal.warnings;
+    options.assertions = optionsGlobal.assertions;
+    imports = let inherit (lib.modules) mkRemovedOptionModule mkRenamedOptionModule; in [
+      (mkRemovedOptionModule [ "extraConfig" ] "Please just add options directly to the server attribute set, cf. the description of `programs.tsmClient.servers`.")
+      (mkRemovedOptionModule [ "text" ] "Please just add options directly to the server attribute set, cf. the description of `programs.tsmClient.servers`.")
+      (mkRenamedOptionModule [ "name" ] [ "servername" ])
+      (mkRenamedOptionModule [ "server" ] [ "tcpserveraddress" ])
+      (mkRenamedOptionModule [ "port" ] [ "tcpport" ])
+      (mkRenamedOptionModule [ "node" ] [ "nodename" ])
+      (mkRenamedOptionModule [ "passwdDir" ] [ "passworddir" ])
+      (mkRenamedOptionModule [ "includeExclude" ] [ "inclexcl" ])
+    ];
   };
 
   options.programs.tsmClient = {
     enable = mkEnableOption (lib.mdDoc ''
-      IBM Spectrum Protect (Tivoli Storage Manager, TSM)
+      IBM Storage Protect (Tivoli Storage Manager, TSM)
       client command line applications with a
       client system-options file "dsm.sys"
     '');
     servers = mkOption {
-      type = attrsOf (submodule [ serverOptions ]);
+      type = attrsOf (submodule serverOptions);
       default = {};
       example.mainTsmServer = {
-        server = "tsmserver.company.com";
-        node = "MY-TSM-NODE";
-        extraConfig.compression = "yes";
+        tcpserveraddress = "tsmserver.company.com";
+        nodename = "MY-TSM-NODE";
+        compression = "yes";
       };
       description = lib.mdDoc ''
         Server definitions ("stanzas")
         for the client system-options file.
+        The name of each entry will be used for
+        the internal `servername` by default.
+        Each attribute will be transformed into a line
+        with a key-value pair within the server's stanza.
+        Integers as values will be
+        canonically turned into strings.
+        The boolean value `true` will be turned
+        into a line with just the attribute's name.
+        The value `null` will not generate a line.
+        A list as values generates an entry for
+        each value, according to the rules above.
       '';
     };
     defaultServername = mkOption {
@@ -215,57 +166,114 @@ let
         TSM-depending packages used on the system.
       '';
     };
-    package = mkOption {
-      type = package;
-      default = pkgs.tsm-client;
-      defaultText = literalExpression "pkgs.tsm-client";
-      example = literalExpression "pkgs.tsm-client-withGui";
-      description = lib.mdDoc ''
-        The TSM client derivation to be
-        added to the system environment.
+    package = mkPackageOption pkgs "tsm-client" {
+      example = "tsm-client-withGui";
+      extraDescription = ''
         It will be used with `.override`
         to add paths to the client system-options file.
       '';
     };
-    wrappedPackage = mkOption {
-      type = package;
-      readOnly = true;
-      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
+    wrappedPackage = mkPackageOption pkgs "tsm-client" {
+      default = null;
+      extraDescription = ''
+        This option is to provide the effective derivation,
+        wrapped with the path to the
+        client system-options file "dsm.sys".
+        It should not be changed, but exists
         for other modules that want to call TSM executables.
       '';
-    };
+    } // { readOnly = true; };
   };
 
   cfg = config.programs.tsmClient;
+  servernames = map (s: s.servername) (attrValues cfg.servers);
 
-  assertions = [
-    {
-      assertion = checkIUnique (mapAttrsToList (k: v: v.name) cfg.servers);
+  assertions =
+    [
+      {
+        assertion = allUnique (map toLower servernames);
+        message = ''
+          TSM server names
+          (option `programs.tsmClient.servers`)
+          contain duplicate name
+          (note that server names are case insensitive).
+        '';
+      }
+      {
+        assertion = (cfg.defaultServername!=null)->(elem cfg.defaultServername servernames);
+        message = ''
+          TSM default server name
+          `programs.tsmClient.defaultServername="${cfg.defaultServername}"`
+          not found in server names in
+          `programs.tsmClient.servers`.
+        '';
+      }
+    ] ++ (mapAttrsToList (name: serverCfg: {
+      assertion = all (key: null != match "[^[:space:]]+" key) (attrNames serverCfg);
       message = ''
-        TSM servernames contain duplicate name
-        (note that case doesn't matter!)
+        TSM server setting names in
+        `programs.tsmClient.servers.${name}.*`
+        contain spaces, but that's not allowed.
+      '';
+    }) cfg.servers) ++ (mapAttrsToList (name: serverCfg: {
+      assertion = allUnique (map toLower (attrNames serverCfg));
+      message = ''
+        TSM server setting names in
+        `programs.tsmClient.servers.${name}.*`
+        contain duplicate names
+        (note that setting names are case insensitive).
+      '';
+    }) cfg.servers)
+    # XXX migration code for freeform settings, this can be removed in 2025:
+    ++ (enrichMigrationInfos "assertions" (addText: { assertion, message }: { inherit assertion; message = addText message; }));
+
+  makeDsmSysLines = key: value:
+    # Turn a key-value pair from the server options attrset
+    # into zero (value==null), one (scalar value) or
+    # more (value is list) configuration stanza lines.
+    if isList value then map (makeDsmSysLines key) value else  # recurse into list
+    if value == null then [ ] else  # skip `null` value
+    [ ("  ${key}${
+      if value == true then "" else  # just output key if value is `true`
+      if isInt value then "  ${builtins.toString value}" else
+      if path.check value then "  \"${value}\"" else  # enclose path in ".."
+      if singleLineStr.check value then "  ${value}" else
+      throw "assertion failed: cannot convert type"  # should never happen
+    }") ];
+
+  makeDsmSysStanza = {servername, ... }@serverCfg:
+    let
+      # drop special values that should not go into server config block
+      attrs = removeAttrs serverCfg [ "servername" "genPasswd"
+        # XXX migration code for freeform settings, these can be removed in 2025:
+        "assertions" "warnings"
+        "extraConfig" "text"
+        "name" "server" "port" "node" "passwdDir" "includeExclude"
+      ];
+    in
+      ''
+        servername  ${servername}
+        ${concatLines (concatLists (mapAttrsToList makeDsmSysLines attrs))}
       '';
-    }
-    {
-      assertion = (cfg.defaultServername!=null)->(hasAttr cfg.defaultServername cfg.servers);
-      message = "TSM defaultServername not found in list of servers";
-    }
-  ];
 
   dsmSysText = ''
-    ****  IBM Spectrum Protect (Tivoli Storage Manager)
+    ****  IBM Storage Protect (Tivoli Storage Manager)
     ****  client system-options file "dsm.sys".
     ****  Do not edit!
     ****  This file is generated by NixOS configuration.
 
     ${optionalString (cfg.defaultServername!=null) "defaultserver  ${cfg.defaultServername}"}
 
-    ${concatLines (mapAttrsToList (k: v: v.stanza) cfg.servers)}
+    ${concatLines (map makeDsmSysStanza (attrValues cfg.servers))}
   '';
 
+  # XXX migration code for freeform settings, this can be removed in 2025:
+  enrichMigrationInfos = what: how: concatLists (
+    mapAttrsToList
+    (name: serverCfg: map (how (text: "In `programs.tsmClient.servers.${name}`: ${text}")) serverCfg."${what}")
+    cfg.servers
+  );
+
 in
 
 {
@@ -280,6 +288,8 @@ in
       dsmSysApi = dsmSysCli;
     };
     environment.systemPackages = [ cfg.wrappedPackage ];
+    # XXX migration code for freeform settings, this can be removed in 2025:
+    warnings = enrichMigrationInfos "warnings" (addText: addText);
   };
 
   meta.maintainers = [ lib.maintainers.yarny ];
diff --git a/nixos/modules/programs/vim.nix b/nixos/modules/programs/vim.nix
index b12a45166d560..da2813f4bb53f 100644
--- a/nixos/modules/programs/vim.nix
+++ b/nixos/modules/programs/vim.nix
@@ -15,14 +15,8 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.vim;
-      defaultText = literalExpression "pkgs.vim";
-      example = literalExpression "pkgs.vim-full";
-      description = lib.mdDoc ''
-        vim package to use.
-      '';
+    package = mkPackageOption pkgs "vim" {
+      example = "vim-full";
     };
   };
 
diff --git a/nixos/modules/programs/virt-manager.nix b/nixos/modules/programs/virt-manager.nix
new file mode 100644
index 0000000000000..095db7586a034
--- /dev/null
+++ b/nixos/modules/programs/virt-manager.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.virt-manager;
+in {
+  options.programs.virt-manager = {
+    enable = lib.mkEnableOption "virt-manager, an UI for managing virtual machines in libvirt";
+
+    package = lib.mkPackageOption pkgs "virt-manager" {};
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    programs.dconf.enable = true;
+  };
+}
diff --git a/nixos/modules/programs/wayland/cardboard.nix b/nixos/modules/programs/wayland/cardboard.nix
new file mode 100644
index 0000000000000..77a094a717005
--- /dev/null
+++ b/nixos/modules/programs/wayland/cardboard.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.cardboard;
+in
+{
+  meta.maintainers = with lib.maintainers; [ AndersonTorres ];
+
+  options.programs.cardboard = {
+    enable = lib.mkEnableOption (lib.mdDoc "cardboard");
+
+    package = lib.mkPackageOption pkgs "cardboard" { };
+  };
+
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    {
+      environment.systemPackages = [ cfg.package ];
+
+      # To make a cardboard session available for certain DMs like SDDM
+      services.xserver.displayManager.sessionPackages = [ cfg.package ];
+    }
+    (import ./wayland-session.nix { inherit lib pkgs; })
+  ]);
+}
diff --git a/nixos/modules/programs/wayland/labwc.nix b/nixos/modules/programs/wayland/labwc.nix
new file mode 100644
index 0000000000000..d0806c3aa5d0e
--- /dev/null
+++ b/nixos/modules/programs/wayland/labwc.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.labwc;
+in
+{
+  meta.maintainers = with lib.maintainers; [ AndersonTorres ];
+
+  options.programs.labwc = {
+    enable = lib.mkEnableOption (lib.mdDoc "labwc");
+    package = lib.mkPackageOption pkgs "labwc" { };
+  };
+
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    {
+      environment.systemPackages = [ cfg.package ];
+
+      xdg.portal.config.wlroots.default = lib.mkDefault [ "wlr" "gtk" ];
+
+      # To make a labwc session available for certain DMs like SDDM
+      services.xserver.displayManager.sessionPackages = [ cfg.package ];
+    }
+    (import ./wayland-session.nix { inherit lib pkgs; })
+  ]);
+}
diff --git a/nixos/modules/programs/wayland/river.nix b/nixos/modules/programs/wayland/river.nix
index 71232a7d2618c..995129b9710ae 100644
--- a/nixos/modules/programs/wayland/river.nix
+++ b/nixos/modules/programs/wayland/river.nix
@@ -10,12 +10,9 @@ in {
   options.programs.river = {
     enable = mkEnableOption (lib.mdDoc "river, a dynamic tiling Wayland compositor");
 
-    package = mkOption {
-      type = with types; nullOr package;
-      default = pkgs.river;
-      defaultText = literalExpression "pkgs.river";
-      description = lib.mdDoc ''
-        River package to use.
+    package = mkPackageOption pkgs "river" {
+      nullable = true;
+      extraDescription = ''
         Set to `null` to not add any River package to your path.
         This should be done if you want to use the Home Manager River module to install River.
       '';
@@ -51,6 +48,9 @@ in {
 
         # To make a river session available if a display manager like SDDM is enabled:
         services.xserver.displayManager.sessionPackages = optionals (cfg.package != null) [ cfg.package ];
+
+        # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
+        xdg.portal.config.river.default = mkDefault [ "wlr" "gtk" ];
       }
       (import ./wayland-session.nix { inherit lib pkgs; })
     ]);
diff --git a/nixos/modules/programs/wayland/sway.nix b/nixos/modules/programs/wayland/sway.nix
index 698d9c2b46c46..57ee629b28810 100644
--- a/nixos/modules/programs/wayland/sway.nix
+++ b/nixos/modules/programs/wayland/sway.nix
@@ -26,13 +26,28 @@ let
     };
   };
 
-  defaultSwayPackage = pkgs.sway.override {
-    extraSessionCommands = cfg.extraSessionCommands;
-    extraOptions = cfg.extraOptions;
-    withBaseWrapper = cfg.wrapperFeatures.base;
-    withGtkWrapper = cfg.wrapperFeatures.gtk;
-    isNixOS = true;
-  };
+  genFinalPackage = pkg:
+    let
+      expectedArgs = lib.naturalSort [
+        "extraSessionCommands"
+        "extraOptions"
+        "withBaseWrapper"
+        "withGtkWrapper"
+        "isNixOS"
+      ];
+      existedArgs = with lib;
+        naturalSort
+        (intersectLists expectedArgs (attrNames (functionArgs pkg.override)));
+    in if existedArgs != expectedArgs then
+      pkg
+    else
+      pkg.override {
+        extraSessionCommands = cfg.extraSessionCommands;
+        extraOptions = cfg.extraOptions;
+        withBaseWrapper = cfg.wrapperFeatures.base;
+        withGtkWrapper = cfg.wrapperFeatures.gtk;
+        isNixOS = true;
+      };
 in {
   options.programs.sway = {
     enable = mkEnableOption (lib.mdDoc ''
@@ -44,14 +59,16 @@ in {
 
     package = mkOption {
       type = with types; nullOr package;
-      default = defaultSwayPackage;
+      default = pkgs.sway;
+      apply = p: if p == null then null else genFinalPackage p;
       defaultText = literalExpression "pkgs.sway";
       description = lib.mdDoc ''
-        Sway package to use. Will override the options
-        'wrapperFeatures', 'extraSessionCommands', and 'extraOptions'.
-        Set to `null` to not add any Sway package to your
-        path. This should be done if you want to use the Home Manager Sway
-        module to install Sway.
+        Sway package to use. If the package does not contain the override arguments
+        `extraSessionCommands`, `extraOptions`, `withBaseWrapper`, `withGtkWrapper`,
+        `isNixOS`, then the module options {option}`wrapperFeatures`,
+        {option}`wrapperFeatures` and {option}`wrapperFeatures` will have no effect.
+        Set to `null` to not add any Sway package to your path. This should be done if
+        you want to use the Home Manager Sway module to install Sway.
       '';
     };
 
@@ -149,6 +166,8 @@ in {
             "sway/config".source = mkOptionDefault "${cfg.package}/etc/sway/config";
           };
         };
+        # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
+        xdg.portal.config.sway.default = mkDefault [ "wlr" "gtk" ];
         # To make a Sway session available if a display manager like SDDM is enabled:
         services.xserver.displayManager.sessionPackages = optionals (cfg.package != null) [ cfg.package ]; }
       (import ./wayland-session.nix { inherit lib pkgs; })
diff --git a/nixos/modules/programs/wayland/waybar.nix b/nixos/modules/programs/wayland/waybar.nix
index 2c49ae1408139..ec60b84f69976 100644
--- a/nixos/modules/programs/wayland/waybar.nix
+++ b/nixos/modules/programs/wayland/waybar.nix
@@ -8,7 +8,7 @@ in
 {
   options.programs.waybar = {
     enable = mkEnableOption (lib.mdDoc "waybar");
-    package = mkPackageOptionMD pkgs "waybar" { };
+    package = mkPackageOption pkgs "waybar" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/programs/wayland/wayfire.nix b/nixos/modules/programs/wayland/wayfire.nix
index d0b280e3940fc..0840246e5e3ef 100644
--- a/nixos/modules/programs/wayland/wayfire.nix
+++ b/nixos/modules/programs/wayland/wayfire.nix
@@ -6,9 +6,9 @@ in
   meta.maintainers = with lib.maintainers; [ rewine ];
 
   options.programs.wayfire = {
-    enable = lib.mkEnableOption (lib.mdDoc "Wayfire, a wayland compositor based on wlroots.");
+    enable = lib.mkEnableOption (lib.mdDoc "Wayfire, a wayland compositor based on wlroots");
 
-    package = lib.mkPackageOptionMD pkgs "wayfire" { };
+    package = lib.mkPackageOption pkgs "wayfire" { };
 
     plugins = lib.mkOption {
       type = lib.types.listOf lib.types.package;
@@ -43,6 +43,8 @@ in
     xdg.portal = {
       enable = lib.mkDefault true;
       wlr.enable = lib.mkDefault true;
+      # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050914
+      config.wayfire.default = lib.mkDefault [ "wlr" "gtk" ];
     };
   };
 }
diff --git a/nixos/modules/programs/weylus.nix b/nixos/modules/programs/weylus.nix
index a5775f3b981cc..f40dfd5c96135 100644
--- a/nixos/modules/programs/weylus.nix
+++ b/nixos/modules/programs/weylus.nix
@@ -26,12 +26,7 @@ in
       '';
     };
 
-    package = mkOption {
-      type = package;
-      default = pkgs.weylus;
-      defaultText = lib.literalExpression "pkgs.weylus";
-      description = lib.mdDoc "Weylus package to install.";
-    };
+    package = mkPackageOption pkgs "weylus" { };
   };
   config = mkIf cfg.enable {
     networking.firewall = mkIf cfg.openFirewall {
diff --git a/nixos/modules/programs/winbox.nix b/nixos/modules/programs/winbox.nix
new file mode 100644
index 0000000000000..1337f57839b02
--- /dev/null
+++ b/nixos/modules/programs/winbox.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg  = config.programs.winbox;
+in
+{
+  options.programs.winbox = {
+    enable = lib.mkEnableOption ("MikroTik Winbox");
+    package = lib.mkPackageOption pkgs "winbox" { };
+
+    openFirewall = lib.mkOption {
+      description = ''
+        Whether to open ports for the MikroTik Neighbor Discovery protocol. Required for Winbox neighbor discovery.
+      '';
+      default = false;
+      type = lib.types.bool;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    networking.firewall.allowedUDPPorts = lib.optionals cfg.openFirewall [ 5678 ];
+  };
+}
diff --git a/nixos/modules/programs/wireshark.nix b/nixos/modules/programs/wireshark.nix
index 834b0ba356955..c0dc349cca4a2 100644
--- a/nixos/modules/programs/wireshark.nix
+++ b/nixos/modules/programs/wireshark.nix
@@ -16,13 +16,8 @@ in {
           setcap wrapper for 'dumpcap' for users in the 'wireshark' group.
         '';
       };
-      package = mkOption {
-        type = types.package;
-        default = pkgs.wireshark-cli;
-        defaultText = literalExpression "pkgs.wireshark-cli";
-        description = lib.mdDoc ''
-          Which Wireshark package to install in the global environment.
-        '';
+      package = mkPackageOption pkgs "wireshark-cli" {
+        example = "wireshark";
       };
     };
   };
diff --git a/nixos/modules/programs/xonsh.nix b/nixos/modules/programs/xonsh.nix
index 167c953f5ffd0..2ece772c929e2 100644
--- a/nixos/modules/programs/xonsh.nix
+++ b/nixos/modules/programs/xonsh.nix
@@ -24,14 +24,8 @@ in
         type = types.bool;
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.xonsh;
-        defaultText = literalExpression "pkgs.xonsh";
-        example = literalExpression "pkgs.xonsh.override { extraPackages = ps: [ ps.requests ]; }";
-        description = lib.mdDoc ''
-          xonsh package to use.
-        '';
+      package = mkPackageOption pkgs "xonsh" {
+        example = "xonsh.override { extraPackages = ps: [ ps.requests ]; }";
       };
 
       config = mkOption {
diff --git a/nixos/modules/programs/yazi.nix b/nixos/modules/programs/yazi.nix
new file mode 100644
index 0000000000000..273a7eeed05fd
--- /dev/null
+++ b/nixos/modules/programs/yazi.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.yazi;
+
+  settingsFormat = pkgs.formats.toml { };
+
+  names = [ "yazi" "theme" "keymap" ];
+in
+{
+  options.programs.yazi = {
+    enable = lib.mkEnableOption (lib.mdDoc "yazi terminal file manager");
+
+    package = lib.mkPackageOption pkgs "yazi" { };
+
+    settings = lib.mkOption {
+      type = with lib.types; submodule {
+        options = lib.listToAttrs (map
+          (name: lib.nameValuePair name (lib.mkOption {
+            inherit (settingsFormat) type;
+            default = { };
+            description = lib.mdDoc ''
+              Configuration included in `${name}.toml`.
+
+              See https://github.com/sxyazi/yazi/blob/v${cfg.package.version}/config/docs/${name}.md for documentation.
+            '';
+          }))
+          names);
+      };
+      default = { };
+      description = lib.mdDoc ''
+        Configuration included in `$YAZI_CONFIG_HOME`.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      systemPackages = [ cfg.package ];
+      variables.YAZI_CONFIG_HOME = "/etc/yazi/";
+      etc = lib.attrsets.mergeAttrsList (map
+        (name: lib.optionalAttrs (cfg.settings.${name} != { }) {
+          "yazi/${name}.toml".source = settingsFormat.generate "${name}.toml" cfg.settings.${name};
+        })
+        names);
+    };
+  };
+  meta = {
+    maintainers = with lib.maintainers; [ linsui ];
+    # The version of the package is used in the doc.
+    buildDocsInSandbox = false;
+  };
+}
diff --git a/nixos/modules/programs/yubikey-touch-detector.nix b/nixos/modules/programs/yubikey-touch-detector.nix
new file mode 100644
index 0000000000000..9a0d107f73c98
--- /dev/null
+++ b/nixos/modules/programs/yubikey-touch-detector.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+let cfg = config.programs.yubikey-touch-detector;
+in {
+  options = {
+    programs.yubikey-touch-detector = {
+      enable = lib.mkEnableOption "yubikey-touch-detector";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [ pkgs.yubikey-touch-detector ];
+
+    systemd.user.services.yubikey-touch-detector = {
+      path = [ pkgs.gnupg ];
+      wantedBy = [ "graphical-session.target" ];
+    };
+    systemd.user.sockets.yubikey-touch-detector = {
+      wantedBy = [ "sockets.target" ];
+    };
+  };
+}
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.md b/nixos/modules/programs/zsh/oh-my-zsh.md
index 73d425244ce79..6a310006edbfc 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.md
+++ b/nixos/modules/programs/zsh/oh-my-zsh.md
@@ -78,7 +78,7 @@ If third-party customizations (e.g. new themes) are supposed to be added to
 
   - Completion scripts are supposed to be stored at
     `$out/share/zsh/site-functions`. This directory is part of the
-    [`fpath`](http://zsh.sourceforge.net/Doc/Release/Functions.html)
+    [`fpath`](https://zsh.sourceforge.io/Doc/Release/Functions.html)
     and the package should be compatible with pure `ZSH`
     setups. The module will automatically link the contents of
     `site-functions` to completions directory in the proper
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix
index 83eee1c88b3ca..09c3bb974a508 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.nix
+++ b/nixos/modules/programs/zsh/oh-my-zsh.nix
@@ -46,15 +46,7 @@ in
           '';
         };
 
-        package = mkOption {
-          default = pkgs.oh-my-zsh;
-          defaultText = literalExpression "pkgs.oh-my-zsh";
-          description = lib.mdDoc ''
-            Package to install for `oh-my-zsh` usage.
-          '';
-
-          type = types.package;
-        };
+        package = mkPackageOption pkgs "oh-my-zsh" { };
 
         plugins = mkOption {
           default = [];
diff --git a/nixos/modules/programs/zsh/zsh-autoenv.nix b/nixos/modules/programs/zsh/zsh-autoenv.nix
index be93c96b2bc85..0894bfc3fdda7 100644
--- a/nixos/modules/programs/zsh/zsh-autoenv.nix
+++ b/nixos/modules/programs/zsh/zsh-autoenv.nix
@@ -8,15 +8,7 @@ in {
   options = {
     programs.zsh.zsh-autoenv = {
       enable = mkEnableOption (lib.mdDoc "zsh-autoenv");
-      package = mkOption {
-        default = pkgs.zsh-autoenv;
-        defaultText = literalExpression "pkgs.zsh-autoenv";
-        description = lib.mdDoc ''
-          Package to install for `zsh-autoenv` usage.
-        '';
-
-        type = types.package;
-      };
+      package = mkPackageOption pkgs "zsh-autoenv" { };
     };
   };
 
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 45014ed3c68ee..3fab863adb7f6 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -54,7 +54,6 @@ in
     (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" "ddclient" ] "ddclient has been removed on the request of the upstream maintainer because it is unmaintained and has bugs. Please switch to a different software like `inadyn` or `knsupdate`.") # Added 2023-07-04
     (mkRemovedOptionModule [ "services" "dnscrypt-proxy" ] "Use services.dnscrypt-proxy2 instead")
     (mkRemovedOptionModule [ "services" "exhibitor" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" ] "The corresponding package was removed from nixpkgs.")
@@ -112,6 +111,7 @@ in
     (mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "rtsp-simple-server" ] "Package has been completely rebranded by upstream as mediamtx, and thus the service and the package were renamed in NixOS as well.")
+    (mkRemovedOptionModule [ "services" "prayer" ] "The corresponding package was removed from nixpkgs.")
 
     (mkRemovedOptionModule [ "i18n" "inputMethod" "fcitx" ] "The fcitx module has been removed. Please use fcitx5 instead")
     (mkRemovedOptionModule [ "services" "dhcpd4" ] ''
@@ -124,6 +124,9 @@ in
       See https://www.isc.org/blogs/isc-dhcp-eol/ for details.
       Please switch to a different implementation like kea or dnsmasq.
     '')
+    (mkRemovedOptionModule [ "services" "tedicross" ] ''
+      The corresponding package was broken and removed from nixpkgs.
+    '')
 
     # Do NOT add any option renames here, see top of the file
   ];
diff --git a/nixos/modules/security/acme/default.md b/nixos/modules/security/acme/default.md
index 8ff97b55f6856..38fbfbf0caece 100644
--- a/nixos/modules/security/acme/default.md
+++ b/nixos/modules/security/acme/default.md
@@ -45,7 +45,7 @@ placeholder certificates in place of the real ACME certs. The placeholder
 certs are overwritten when the ACME certs arrive. For
 `foo.example.com` the config would look like this:
 
-```
+```nix
 security.acme.acceptTerms = true;
 security.acme.defaults.email = "admin+acme@example.com";
 services.nginx = {
@@ -72,7 +72,7 @@ services.nginx = {
       };
     };
   };
-}
+};
 ```
 
 ## Using ACME certificates in Apache/httpd {#module-security-acme-httpd}
@@ -88,7 +88,7 @@ This example uses a vhost called `certs.example.com`, with
 the intent that you will generate certs for all your vhosts and redirect
 everyone to HTTPS.
 
-```
+```nix
 security.acme.acceptTerms = true;
 security.acme.defaults.email = "admin+acme@example.com";
 
@@ -111,7 +111,7 @@ services.nginx = {
       };
     };
   };
-}
+};
 # Alternative config for Apache
 users.users.wwwrun.extraGroups = [ "acme" ];
 services.httpd = {
@@ -131,12 +131,12 @@ services.httpd = {
       '';
     };
   };
-}
+};
 ```
 
 Now you need to configure ACME to generate a certificate.
 
-```
+```nix
 security.acme.certs."foo.example.com" = {
   webroot = "/var/lib/acme/.challenges";
   email = "foo@example.com";
@@ -167,7 +167,7 @@ see the [lego docs](https://go-acme.github.io/lego/dns/)
 for provider/server specific configuration values. For the sake of these
 docs, we will provide a fully self-hosted example using bind.
 
-```
+```nix
 services.bind = {
   enable = true;
   extraConfig = ''
@@ -181,7 +181,7 @@ services.bind = {
       extraConfig = "allow-update { key rfc2136key.example.com.; };";
     }
   ];
-}
+};
 
 # Now we can configure ACME
 security.acme.acceptTerms = true;
@@ -189,7 +189,7 @@ security.acme.defaults.email = "admin+acme@example.com";
 security.acme.certs."example.com" = {
   domain = "*.example.com";
   dnsProvider = "rfc2136";
-  credentialsFile = "/var/lib/secrets/certs.secret";
+  environmentFile = "/var/lib/secrets/certs.secret";
   # We don't need to wait for propagation since this is a local DNS server
   dnsPropagationCheck = false;
 };
@@ -199,7 +199,7 @@ The {file}`dnskeys.conf` and {file}`certs.secret`
 must be kept secure and thus you should not keep their contents in your
 Nix config. Instead, generate them one time with a systemd service:
 
-```
+```nix
 systemd.services.dns-rfc2136-conf = {
   requiredBy = ["acme-example.com.service" "bind.service"];
   before = ["acme-example.com.service" "bind.service"];
@@ -250,13 +250,13 @@ first, however instead of setting the options for one certificate
 you will set them as defaults
 (e.g. [](#opt-security.acme.defaults.dnsProvider)).
 
-```
+```nix
 # Configure ACME appropriately
 security.acme.acceptTerms = true;
 security.acme.defaults.email = "admin+acme@example.com";
 security.acme.defaults = {
   dnsProvider = "rfc2136";
-  credentialsFile = "/var/lib/secrets/certs.secret";
+  environmentFile = "/var/lib/secrets/certs.secret";
   # We don't need to wait for propagation since this is a local DNS server
   dnsPropagationCheck = false;
 };
@@ -271,7 +271,7 @@ services.nginx = {
       acmeRoot = null;
     };
   };
-}
+};
 ```
 
 And that's it! Next time your configuration is rebuilt, or when
@@ -287,7 +287,7 @@ There is no way to change the user the ACME module uses (it will always be
 Below is an example configuration for OpenSMTPD, but this pattern
 can be applied to any service.
 
-```
+```nix
 # Configure ACME however you like (DNS or HTTP validation), adding
 # the following configuration for the relevant certificate.
 # Note: You cannot use `systemctl reload` here as that would mean
@@ -340,7 +340,7 @@ to be regenerated. In this scenario lego will produce the error `JWS verificatio
 The solution is to simply delete the associated accounts file and
 re-run the affected service(s).
 
-```
+```shell
 # Find the accounts folder for the certificate
 systemctl cat acme-example.com.service | grep -Po 'accounts/[^:]*'
 export accountdir="$(!!)"
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix
index 8aee71c864d0a..40d9c487996b5 100644
--- a/nixos/modules/security/acme/default.nix
+++ b/nixos/modules/security/acme/default.nix
@@ -1,6 +1,8 @@
 { config, lib, pkgs, options, ... }:
 with lib;
 let
+
+
   cfg = config.security.acme;
   opt = options.security.acme;
   user = if cfg.useRoot then "root" else "acme";
@@ -14,6 +16,36 @@ let
   mkAccountHash = acmeServer: data: mkHash "${toString acmeServer} ${data.keyType} ${data.email}";
   accountDirRoot = "/var/lib/acme/.lego/accounts/";
 
+  lockdir = "/run/acme/";
+  concurrencyLockfiles = map (n: "${toString n}.lock") (lib.range 1 cfg.maxConcurrentRenewals);
+  # Assign elements of `baseList` to each element of `needAssignmentList`, until the latter is exhausted.
+  # returns: [{fst = "element of baseList"; snd = "element of needAssignmentList"}]
+  roundRobinAssign = baseList: needAssignmentList:
+    if baseList == [] then []
+    else _rrCycler baseList baseList needAssignmentList;
+  _rrCycler = with builtins; origBaseList: workingBaseList: needAssignmentList:
+    if (workingBaseList == [] || needAssignmentList == [])
+    then []
+    else
+      [{ fst = head workingBaseList; snd = head needAssignmentList;}] ++
+      _rrCycler origBaseList (if (tail workingBaseList == []) then origBaseList else tail workingBaseList) (tail needAssignmentList);
+  attrsToList = mapAttrsToList (attrname: attrval: {name = attrname; value = attrval;});
+  # for an AttrSet `funcsAttrs` having functions as values, apply single arguments from
+  # `argsList` to them in a round-robin manner.
+  # Returns an attribute set with the applied functions as values.
+  roundRobinApplyAttrs = funcsAttrs: argsList: lib.listToAttrs (map (x: {inherit (x.snd) name; value = x.snd.value x.fst;}) (roundRobinAssign argsList (attrsToList funcsAttrs)));
+  wrapInFlock = lockfilePath: script:
+    # explainer: https://stackoverflow.com/a/60896531
+    ''
+      exec {LOCKFD}> ${lockfilePath}
+      echo "Waiting to acquire lock ${lockfilePath}"
+      ${pkgs.flock}/bin/flock ''${LOCKFD} || exit 1
+      echo "Acquired lock ${lockfilePath}"
+    ''
+    + script + "\n"
+    + ''echo "Releasing lock ${lockfilePath}"  # only released after process exit'';
+
+
   # There are many services required to make cert renewals work.
   # They all follow a common structure:
   #   - They inherit this commonServiceConfig
@@ -31,6 +63,7 @@ let
     ProtectSystem = "strict";
     ReadWritePaths = [
       "/var/lib/acme"
+      lockdir
     ];
     PrivateTmp = true;
 
@@ -118,7 +151,8 @@ let
       # We don't want this to run every time a renewal happens
       RemainAfterExit = true;
 
-      # These StateDirectory entries negate the need for tmpfiles
+      # StateDirectory entries are a cleaner, service-level mechanism
+      # for dealing with persistent service data
       StateDirectory = [ "acme" "acme/.lego" "acme/.lego/accounts" ];
       StateDirectoryMode = 755;
       WorkingDirectory = "/var/lib/acme";
@@ -127,10 +161,30 @@ let
       ExecStart = "+" + (pkgs.writeShellScript "acme-fixperms" script);
     };
   };
+  lockfilePrepareService = {
+    description = "Manage lock files for acme services";
+
+    # ensure all required lock files exist, but none more
+    script = ''
+      GLOBIGNORE="${concatStringsSep ":" concurrencyLockfiles}"
+      rm -f *
+      unset GLOBIGNORE
+
+      xargs touch <<< "${toString concurrencyLockfiles}"
+    '';
+
+    serviceConfig = commonServiceConfig // {
+      # We don't want this to run every time a renewal happens
+      RemainAfterExit = true;
+      WorkingDirectory = lockdir;
+    };
+  };
+
 
   certToConfig = cert: data: let
     acmeServer = data.server;
     useDns = data.dnsProvider != null;
+    useDnsOrS3 = useDns || data.s3Bucket != null;
     destPath = "/var/lib/acme/${cert}";
     selfsignedDeps = optionals (cfg.preliminarySelfsigned) [ "acme-selfsigned-${cert}.service" ];
 
@@ -166,7 +220,8 @@ let
       [ "--dns" data.dnsProvider ]
       ++ optionals (!data.dnsPropagationCheck) [ "--dns.disable-cp" ]
       ++ optionals (data.dnsResolver != null) [ "--dns.resolvers" data.dnsResolver ]
-    ) else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ]
+    ) else if data.s3Bucket != null then [ "--http" "--http.s3-bucket" data.s3Bucket ]
+    else if data.listenHTTP != null then [ "--http" "--http.port" data.listenHTTP ]
     else [ "--http" "--http.webroot" data.webroot ];
 
     commonOpts = [
@@ -229,10 +284,10 @@ let
       };
     };
 
-    selfsignService = {
+    selfsignService = lockfileName: {
       description = "Generate self-signed certificate for ${cert}";
-      after = [ "acme-selfsigned-ca.service" "acme-fixperms.service" ];
-      requires = [ "acme-selfsigned-ca.service" "acme-fixperms.service" ];
+      after = [ "acme-selfsigned-ca.service" "acme-fixperms.service" ] ++ optional (cfg.maxConcurrentRenewals > 0) "acme-lockfiles.service";
+      requires = [ "acme-selfsigned-ca.service" "acme-fixperms.service" ] ++ optional (cfg.maxConcurrentRenewals > 0) "acme-lockfiles.service";
 
       path = with pkgs; [ minica ];
 
@@ -256,7 +311,7 @@ let
       # Working directory will be /tmp
       # minica will output to a folder sharing the name of the first domain
       # in the list, which will be ${data.domain}
-      script = ''
+      script = (if (lockfileName == null) then lib.id else wrapInFlock "${lockdir}${lockfileName}") ''
         minica \
           --ca-key ca/key.pem \
           --ca-cert ca/cert.pem \
@@ -277,10 +332,10 @@ let
       '';
     };
 
-    renewService = {
+    renewService = lockfileName: {
       description = "Renew ACME certificate for ${cert}";
-      after = [ "network.target" "network-online.target" "acme-fixperms.service" "nss-lookup.target" ] ++ selfsignedDeps;
-      wants = [ "network-online.target" "acme-fixperms.service" ] ++ selfsignedDeps;
+      after = [ "network.target" "network-online.target" "acme-fixperms.service" "nss-lookup.target" ] ++ selfsignedDeps ++ optional (cfg.maxConcurrentRenewals > 0) "acme-lockfiles.service";
+      wants = [ "network-online.target" "acme-fixperms.service" ] ++ selfsignedDeps ++ optional (cfg.maxConcurrentRenewals > 0) "acme-lockfiles.service";
 
       # https://github.com/NixOS/nixpkgs/pull/81371#issuecomment-605526099
       wantedBy = optionals (!config.boot.isContainer) [ "multi-user.target" ];
@@ -290,6 +345,10 @@ let
       serviceConfig = commonServiceConfig // {
         Group = data.group;
 
+        # Let's Encrypt Failed Validation Limit allows 5 retries per hour, per account, hostname and hour.
+        # This avoids eating them all up if something is misconfigured upon the first try.
+        RestartSec = 15 * 60;
+
         # Keep in mind that these directories will be deleted if the user runs
         # systemctl clean --what=state
         # acme/.lego/${cert} is listed for this reason.
@@ -309,8 +368,13 @@ let
           "/var/lib/acme/.lego/${cert}/${certDir}:/tmp/certificates"
         ];
 
-        # Only try loading the credentialsFile if the dns challenge is enabled
-        EnvironmentFile = mkIf useDns data.credentialsFile;
+        EnvironmentFile = mkIf useDnsOrS3 data.environmentFile;
+
+        Environment = mkIf useDnsOrS3
+          (mapAttrsToList (k: v: ''"${k}=%d/${k}"'') data.credentialFiles);
+
+        LoadCredential = mkIf useDnsOrS3
+          (mapAttrsToList (k: v: "${k}:${v}") data.credentialFiles);
 
         # Run as root (Prefixed with +)
         ExecStartPost = "+" + (pkgs.writeShellScript "acme-postrun" ''
@@ -329,7 +393,7 @@ let
       };
 
       # Working directory will be /tmp
-      script = ''
+      script = (if (lockfileName == null) then lib.id else wrapInFlock "${lockdir}${lockfileName}") ''
         ${optionalString data.enableDebugLogs "set -x"}
         set -euo pipefail
 
@@ -443,6 +507,10 @@ let
       defaultText = if isDefaults then default else literalExpression "config.security.acme.defaults.${name}";
     };
   in {
+    imports = [
+      (mkRenamedOptionModule [ "credentialsFile" ] [ "environmentFile" ])
+    ];
+
     options = {
       validMinDays = mkOption {
         type = types.int;
@@ -529,7 +597,7 @@ let
         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 <https://go-acme.github.io/lego/usage/cli/#usage>.
+          at <https://go-acme.github.io/lego/usage/cli/options/>.
         '';
       };
 
@@ -554,9 +622,9 @@ let
         '';
       };
 
-      credentialsFile = mkOption {
+      environmentFile = mkOption {
         type = types.nullOr types.path;
-        inherit (defaultAndText "credentialsFile" null) default defaultText;
+        inherit (defaultAndText "environmentFile" null) default defaultText;
         description = lib.mdDoc ''
           Path to an EnvironmentFile for the cert's service containing any required and
           optional environment variables for your selected dnsProvider.
@@ -566,6 +634,24 @@ let
         example = "/var/src/secrets/example.org-route53-api-token";
       };
 
+      credentialFiles = mkOption {
+        type = types.attrsOf (types.path);
+        inherit (defaultAndText "credentialFiles" {}) default defaultText;
+        description = lib.mdDoc ''
+          Environment variables suffixed by "_FILE" to set for the cert's service
+          for your selected dnsProvider.
+          To find out what values you need to set, consult the documentation at
+          <https://go-acme.github.io/lego/dns/> for the corresponding dnsProvider.
+          This allows to securely pass credential files to lego by leveraging systemd
+          credentials.
+        '';
+        example = literalExpression ''
+          {
+            "RFC2136_TSIG_SECRET_FILE" = "/run/secrets/tsig-secret-example.org";
+          }
+        '';
+      };
+
       dnsPropagationCheck = mkOption {
         type = types.bool;
         inherit (defaultAndText "dnsPropagationCheck" true) default defaultText;
@@ -674,6 +760,15 @@ let
         '';
       };
 
+      s3Bucket = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "acme";
+        description = lib.mdDoc ''
+          S3 bucket name to use for HTTP-01 based challenges. Challenges will be written to the S3 bucket.
+        '';
+      };
+
       inheritDefaults = mkOption {
         default = true;
         example = true;
@@ -755,6 +850,17 @@ in {
           }
         '';
       };
+      maxConcurrentRenewals = mkOption {
+        default = 5;
+        type = types.int;
+        description = lib.mdDoc ''
+          Maximum number of concurrent certificate generation or renewal jobs. All other
+          jobs will queue and wait running jobs to finish. Reduces the system load of
+          certificate generation.
+
+          Set to `0` to allow unlimited number of concurrent job runs."
+          '';
+      };
     };
   };
 
@@ -791,10 +897,10 @@ in {
         certs = attrValues cfg.certs;
       in [
         {
-          assertion = cfg.email != null || all (certOpts: certOpts.email != null) certs;
+          assertion = cfg.defaults.email != null || all (certOpts: certOpts.email != null) certs;
           message = ''
             You must define `security.acme.certs.<name>.email` or
-            `security.acme.email` to register with the CA. Note that using
+            `security.acme.defaults.email` to register with the CA. Note that using
             many different addresses for certs may trigger account rate limits.
           '';
         }
@@ -836,33 +942,25 @@ in {
             and remove the wildcard from the path.
           '';
         }
-        {
-          assertion = data.dnsProvider == null || data.webroot == null;
-          message = ''
-            Options `security.acme.certs.${cert}.dnsProvider` and
-            `security.acme.certs.${cert}.webroot` are mutually exclusive.
-          '';
-        }
-        {
-          assertion = data.webroot == null || data.listenHTTP == null;
-          message = ''
-            Options `security.acme.certs.${cert}.webroot` and
-            `security.acme.certs.${cert}.listenHTTP` are mutually exclusive.
-          '';
-        }
-        {
-          assertion = data.listenHTTP == null || data.dnsProvider == null;
+        (let exclusiveAttrs = {
+          inherit (data) dnsProvider webroot listenHTTP s3Bucket;
+        }; in {
+          assertion = lib.length (lib.filter (x: x != null) (builtins.attrValues exclusiveAttrs)) == 1;
           message = ''
-            Options `security.acme.certs.${cert}.listenHTTP` and
-            `security.acme.certs.${cert}.dnsProvider` are mutually exclusive.
+            Exactly one of the options
+            `security.acme.certs.${cert}.dnsProvider`,
+            `security.acme.certs.${cert}.webroot`,
+            `security.acme.certs.${cert}.listenHTTP` and
+            `security.acme.certs.${cert}.s3Bucket`
+            is required.
+            Current values: ${(lib.generators.toPretty {} exclusiveAttrs)}.
           '';
-        }
+        })
         {
-          assertion = data.dnsProvider != null || data.webroot != null || data.listenHTTP != null;
+          assertion = all (hasSuffix "_FILE") (attrNames data.credentialFiles);
           message = ''
-            One of `security.acme.certs.${cert}.dnsProvider`,
-            `security.acme.certs.${cert}.webroot`, or
-            `security.acme.certs.${cert}.listenHTTP` must be provided.
+            Option `security.acme.certs.${cert}.credentialFiles` can only be
+            used for variables suffixed by "_FILE".
           '';
         }
       ]) cfg.certs));
@@ -875,12 +973,28 @@ in {
 
       users.groups.acme = {};
 
-      systemd.services = {
-        "acme-fixperms" = userMigrationService;
-      } // (mapAttrs' (cert: conf: nameValuePair "acme-${cert}" conf.renewService) certConfigs)
+      # for lock files, still use tmpfiles as they should better reside in /run
+      systemd.tmpfiles.rules = [
+        "d ${lockdir} 0700 ${user} - - -"
+        "Z ${lockdir} 0700 ${user} - - -"
+      ];
+
+      systemd.services = let
+        renewServiceFunctions = mapAttrs' (cert: conf: nameValuePair "acme-${cert}" conf.renewService) certConfigs;
+        renewServices =  if cfg.maxConcurrentRenewals > 0
+          then roundRobinApplyAttrs renewServiceFunctions concurrencyLockfiles
+          else mapAttrs (_: f: f null) renewServiceFunctions;
+        selfsignServiceFunctions = mapAttrs' (cert: conf: nameValuePair "acme-selfsigned-${cert}" conf.selfsignService) certConfigs;
+        selfsignServices = if cfg.maxConcurrentRenewals > 0
+          then roundRobinApplyAttrs selfsignServiceFunctions concurrencyLockfiles
+          else mapAttrs (_: f: f null) selfsignServiceFunctions;
+        in
+        { "acme-fixperms" = userMigrationService; }
+        // (optionalAttrs (cfg.maxConcurrentRenewals > 0) {"acme-lockfiles" = lockfilePrepareService; })
+        // renewServices
         // (optionalAttrs (cfg.preliminarySelfsigned) ({
         "acme-selfsigned-ca" = selfsignCAService;
-      } // (mapAttrs' (cert: conf: nameValuePair "acme-selfsigned-${cert}" conf.selfsignService) certConfigs)));
+      } // selfsignServices));
 
       systemd.timers = mapAttrs' (cert: conf: nameValuePair "acme-${cert}" conf.renewTimer) certConfigs;
 
diff --git a/nixos/modules/security/apparmor.nix b/nixos/modules/security/apparmor.nix
index 24b48338ed772..ea1af6c6e2f29 100644
--- a/nixos/modules/security/apparmor.nix
+++ b/nixos/modules/security/apparmor.nix
@@ -164,7 +164,8 @@ in
         "local-fs.target"
         "systemd-journald-audit.socket"
       ];
-      before = [ "sysinit.target" ];
+      before = [ "sysinit.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
       wantedBy = [ "multi-user.target" ];
       unitConfig = {
         Description="Load AppArmor policies";
diff --git a/nixos/modules/security/apparmor/profiles.nix b/nixos/modules/security/apparmor/profiles.nix
index 8eb630b5a48a5..0bf90a0086556 100644
--- a/nixos/modules/security/apparmor/profiles.nix
+++ b/nixos/modules/security/apparmor/profiles.nix
@@ -2,10 +2,4 @@
 let apparmor = config.security.apparmor; in
 {
 config.security.apparmor.packages = [ pkgs.apparmor-profiles ];
-config.security.apparmor.policies."bin.ping".profile = lib.mkIf apparmor.policies."bin.ping".enable ''
-  include "${pkgs.iputils.apparmor}/bin.ping"
-  include "${pkgs.inetutils.apparmor}/bin.ping"
-  # Note that including those two profiles in the same profile
-  # would not work if the second one were to re-include <tunables/global>.
-'';
 }
diff --git a/nixos/modules/security/auditd.nix b/nixos/modules/security/auditd.nix
index db4b2701ee2e9..253ee1d4dd0e5 100644
--- a/nixos/modules/security/auditd.nix
+++ b/nixos/modules/security/auditd.nix
@@ -13,6 +13,8 @@ with lib;
     systemd.services.auditd = {
       description = "Linux Audit daemon";
       wantedBy = [ "basic.target" ];
+      before = [ "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
 
       unitConfig = {
         ConditionVirtualization = "!container";
@@ -23,7 +25,7 @@ with lib;
       path = [ pkgs.audit ];
 
       serviceConfig = {
-        ExecStartPre="${pkgs.coreutils}/bin/mkdir -p /var/log/audit";
+        ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/log/audit";
         ExecStart = "${pkgs.audit}/bin/auditd -l -n -s nochange";
       };
     };
diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix
index 02b11766b3c09..ef76bfeb6d66a 100644
--- a/nixos/modules/security/duosec.nix
+++ b/nixos/modules/security/duosec.nix
@@ -193,8 +193,12 @@ in
         source = "${pkgs.duo-unix.out}/bin/login_duo";
       };
 
-    system.activationScripts = {
-      login_duo = mkIf cfg.ssh.enable ''
+    systemd.services.login-duo = lib.mkIf cfg.ssh.enable {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      script = ''
         if test -f "${cfg.secretKeyFile}"; then
           mkdir -m 0755 -p /etc/duo
 
@@ -209,7 +213,14 @@ in
           mv -fT "$conf" /etc/duo/login_duo.conf
         fi
       '';
-      pam_duo = mkIf cfg.pam.enable ''
+    };
+
+    systemd.services.pam-duo = lib.mkIf cfg.ssh.enable {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      script = ''
         if test -f "${cfg.secretKeyFile}"; then
           mkdir -m 0755 -p /etc/duo
 
diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix
index f75b4df1851a5..95975943ff801 100644
--- a/nixos/modules/security/google_oslogin.nix
+++ b/nixos/modules/security/google_oslogin.nix
@@ -42,6 +42,10 @@ in
     security.sudo.extraConfig = ''
       #includedir /run/google-sudoers.d
     '';
+    security.sudo-rs.extraConfig = ''
+      #includedir /run/google-sudoers.d
+    '';
+
     systemd.tmpfiles.rules = [
       "d /run/google-sudoers.d 750 root root -"
       "d /var/google-users.d 750 root root -"
diff --git a/nixos/modules/security/ipa.nix b/nixos/modules/security/ipa.nix
index 69a670cd5e4a3..3bf8b11f86261 100644
--- a/nixos/modules/security/ipa.nix
+++ b/nixos/modules/security/ipa.nix
@@ -117,8 +117,8 @@ in {
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = !config.krb5.enable;
-        message = "krb5 must be disabled through `krb5.enable` for FreeIPA integration to work.";
+        assertion = !config.security.krb5.enable;
+        message = "krb5 must be disabled through `security.krb5.enable` for FreeIPA integration to work.";
       }
       {
         assertion = !config.users.ldap.enable;
@@ -181,25 +181,33 @@ in {
       '';
     };
 
-    system.activationScripts.ipa = stringAfter ["etc"] ''
-      # libcurl requires a hard copy of the certificate
-      if ! ${pkgs.diffutils}/bin/diff ${cfg.certificate} /etc/ipa/ca.crt > /dev/null 2>&1; then
-        rm -f /etc/ipa/ca.crt
-        cp ${cfg.certificate} /etc/ipa/ca.crt
-      fi
-
-      if [ ! -f /etc/krb5.keytab ]; then
-        cat <<EOF
-
-          In order to complete FreeIPA integration, please join the domain by completing the following steps:
-          1. Authenticate as an IPA user authorized to join new hosts, e.g. kinit admin@${cfg.realm}
-          2. Join the domain and obtain the keytab file: ipa-join
-          3. Install the keytab file: sudo install -m 600 krb5.keytab /etc/
-          4. Restart sssd systemd service: sudo systemctl restart sssd
-
-      EOF
-      fi
-    '';
+    systemd.services."ipa-activation" = {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      script = ''
+        # libcurl requires a hard copy of the certificate
+        if ! ${pkgs.diffutils}/bin/diff ${cfg.certificate} /etc/ipa/ca.crt > /dev/null 2>&1; then
+          rm -f /etc/ipa/ca.crt
+          cp ${cfg.certificate} /etc/ipa/ca.crt
+        fi
+
+        if [ ! -f /etc/krb5.keytab ]; then
+          cat <<EOF
+
+            In order to complete FreeIPA integration, please join the domain by completing the following steps:
+            1. Authenticate as an IPA user authorized to join new hosts, e.g. kinit admin@${cfg.realm}
+            2. Join the domain and obtain the keytab file: ipa-join
+            3. Install the keytab file: sudo install -m 600 krb5.keytab /etc/
+            4. Restart sssd systemd service: sudo systemctl restart sssd
+
+        EOF
+        fi
+      '';
+    };
 
     services.sssd.config = ''
       [domain/${cfg.domain}]
diff --git a/nixos/modules/security/krb5/default.nix b/nixos/modules/security/krb5/default.nix
new file mode 100644
index 0000000000000..5921982f954ca
--- /dev/null
+++ b/nixos/modules/security/krb5/default.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+let
+  inherit (lib) mdDoc mkIf mkOption mkPackageOption mkRemovedOptionModule;
+  inherit (lib.types) bool;
+
+  mkRemovedOptionModule' = name: reason: mkRemovedOptionModule ["krb5" name] reason;
+  mkRemovedOptionModuleCfg = name: mkRemovedOptionModule' name ''
+    The option `krb5.${name}' has been removed. Use
+    `security.krb5.settings.${name}' for structured configuration.
+  '';
+
+  cfg = config.security.krb5;
+  format = import ./krb5-conf-format.nix { inherit pkgs lib; } { };
+in {
+  imports = [
+    (mkRemovedOptionModuleCfg "libdefaults")
+    (mkRemovedOptionModuleCfg "realms")
+    (mkRemovedOptionModuleCfg "domain_realm")
+    (mkRemovedOptionModuleCfg "capaths")
+    (mkRemovedOptionModuleCfg "appdefaults")
+    (mkRemovedOptionModuleCfg "plugins")
+    (mkRemovedOptionModuleCfg "config")
+    (mkRemovedOptionModuleCfg "extraConfig")
+    (mkRemovedOptionModule' "kerberos" ''
+      The option `krb5.kerberos' has been moved to `security.krb5.package'.
+    '')
+  ];
+
+  options = {
+    security.krb5 = {
+      enable = mkOption {
+        default = false;
+        description = mdDoc "Enable and configure Kerberos utilities";
+        type = bool;
+      };
+
+      package = mkPackageOption pkgs "krb5" {
+        example = "heimdal";
+      };
+
+      settings = mkOption {
+        default = { };
+        type = format.type;
+        description = mdDoc ''
+          Structured contents of the {file}`krb5.conf` file. See
+          {manpage}`krb5.conf(5)` for details about configuration.
+        '';
+        example = {
+          include = [ "/run/secrets/secret-krb5.conf" ];
+          includedir = [ "/run/secrets/secret-krb5.conf.d" ];
+
+          libdefaults = {
+            default_realm = "ATHENA.MIT.EDU";
+          };
+
+          realms = {
+            "ATHENA.MIT.EDU" = {
+              admin_server = "athena.mit.edu";
+              kdc = [
+                "athena01.mit.edu"
+                "athena02.mit.edu"
+              ];
+            };
+          };
+
+          domain_realm = {
+            "mit.edu" = "ATHENA.MIT.EDU";
+          };
+
+          logging = {
+            kdc = "SYSLOG:NOTICE";
+            admin_server = "SYSLOG:NOTICE";
+            default = "SYSLOG:NOTICE";
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment = {
+      systemPackages = [ cfg.package ];
+      etc."krb5.conf".source = format.generate "krb5.conf" cfg.settings;
+    };
+  };
+
+  meta.maintainers = builtins.attrValues {
+    inherit (lib.maintainers) dblsaiko h7x4;
+  };
+}
diff --git a/nixos/modules/security/krb5/krb5-conf-format.nix b/nixos/modules/security/krb5/krb5-conf-format.nix
new file mode 100644
index 0000000000000..d01e47a40be05
--- /dev/null
+++ b/nixos/modules/security/krb5/krb5-conf-format.nix
@@ -0,0 +1,88 @@
+{ pkgs, lib, ... }:
+
+# Based on
+# - https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html
+# - https://manpages.debian.org/unstable/heimdal-docs/krb5.conf.5heimdal.en.html
+
+let
+  inherit (lib) boolToString concatMapStringsSep concatStringsSep filter
+    isAttrs isBool isList mapAttrsToList mdDoc mkOption singleton splitString;
+  inherit (lib.types) attrsOf bool coercedTo either int listOf oneOf path
+    str submodule;
+in
+{ }: {
+  type = let
+    section = attrsOf relation;
+    relation = either (attrsOf value) value;
+    value = either (listOf atom) atom;
+    atom = oneOf [int str bool];
+  in submodule {
+    freeformType = attrsOf section;
+    options = {
+      include = mkOption {
+        default = [ ];
+        description = mdDoc ''
+          Files to include in the Kerberos configuration.
+        '';
+        type = coercedTo path singleton (listOf path);
+      };
+      includedir = mkOption {
+        default = [ ];
+        description = mdDoc ''
+          Directories containing files to include in the Kerberos configuration.
+        '';
+        type = coercedTo path singleton (listOf path);
+      };
+      module = mkOption {
+        default = [ ];
+        description = mdDoc ''
+          Modules to obtain Kerberos configuration from.
+        '';
+        type = coercedTo path singleton (listOf path);
+      };
+    };
+  };
+
+  generate = let
+    indent = str: concatMapStringsSep "\n" (line: "  " + line) (splitString "\n" str);
+
+    formatToplevel = args @ {
+      include ? [ ],
+      includedir ? [ ],
+      module ? [ ],
+      ...
+    }: let
+      sections = removeAttrs args [ "include" "includedir" "module" ];
+    in concatStringsSep "\n" (filter (x: x != "") [
+      (concatStringsSep "\n" (mapAttrsToList formatSection sections))
+      (concatMapStringsSep "\n" (m: "module ${m}") module)
+      (concatMapStringsSep "\n" (i: "include ${i}") include)
+      (concatMapStringsSep "\n" (i: "includedir ${i}") includedir)
+    ]);
+
+    formatSection = name: section: ''
+      [${name}]
+      ${indent (concatStringsSep "\n" (mapAttrsToList formatRelation section))}
+    '';
+
+    formatRelation = name: relation:
+      if isAttrs relation
+      then ''
+        ${name} = {
+        ${indent (concatStringsSep "\n" (mapAttrsToList formatValue relation))}
+        }''
+      else formatValue name relation;
+
+    formatValue = name: value:
+      if isList value
+      then concatMapStringsSep "\n" (formatAtom name) value
+      else formatAtom name value;
+
+    formatAtom = name: atom: let
+      v = if isBool atom then boolToString atom else toString atom;
+    in "${name} = ${v}";
+  in
+    name: value: pkgs.writeText name ''
+      ${formatToplevel value}
+    '';
+}
diff --git a/nixos/modules/security/lock-kernel-modules.nix b/nixos/modules/security/lock-kernel-modules.nix
index 333b648014263..461b9ffe7ee0b 100644
--- a/nixos/modules/security/lock-kernel-modules.nix
+++ b/nixos/modules/security/lock-kernel-modules.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:
+{ config, lib, ... }:
 
 with lib;
 
@@ -49,7 +49,7 @@ with lib;
         };
 
       script = ''
-        ${pkgs.udev}/bin/udevadm settle
+        ${config.systemd.package}/bin/udevadm settle
         echo -n 1 >/proc/sys/kernel/modules_disabled
       '';
     };
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index a431817fe1bb3..111be7057afc0 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -6,6 +6,92 @@
 with lib;
 
 let
+
+  mkRulesTypeOption = type: mkOption {
+    # These options are experimental and subject to breaking changes without notice.
+    description = lib.mdDoc ''
+      PAM `${type}` rules for this service.
+
+      Attribute keys are the name of each rule.
+    '';
+    type = types.attrsOf (types.submodule ({ name, config, ... }: {
+      options = {
+        name = mkOption {
+          type = types.str;
+          description = lib.mdDoc ''
+            Name of this rule.
+          '';
+          internal = true;
+          readOnly = true;
+        };
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether this rule is added to the PAM service config file.
+          '';
+        };
+        order = mkOption {
+          type = types.int;
+          description = lib.mdDoc ''
+            Order of this rule in the service file. Rules are arranged in ascending order of this value.
+
+            ::: {.warning}
+            The `order` values for the built-in rules are subject to change. If you assign a constant value to this option, a system update could silently reorder your rule. You could be locked out of your system, or your system could be left wide open. When using this option, set it to a relative offset from another rule's `order` value:
+
+            ```nix
+            {
+              security.pam.services.login.rules.auth.foo.order =
+                config.security.pam.services.login.rules.auth.unix.order + 10;
+            }
+            ```
+            :::
+          '';
+        };
+        control = mkOption {
+          type = types.str;
+          description = lib.mdDoc ''
+            Indicates the behavior of the PAM-API should the module fail to succeed in its authentication task. See `control` in {manpage}`pam.conf(5)` for details.
+          '';
+        };
+        modulePath = mkOption {
+          type = types.str;
+          description = lib.mdDoc ''
+            Either the full filename of the PAM to be used by the application (it begins with a '/'), or a relative pathname from the default module location. See `module-path` in {manpage}`pam.conf(5)` for details.
+          '';
+        };
+        args = mkOption {
+          type = types.listOf types.str;
+          description = lib.mdDoc ''
+            Tokens that can be used to modify the specific behavior of the given PAM. Such arguments will be documented for each individual module. See `module-arguments` in {manpage}`pam.conf(5)` for details.
+
+            Escaping rules for spaces and square brackets are automatically applied.
+
+            {option}`settings` are automatically added as {option}`args`. It's recommended to use the {option}`settings` option whenever possible so that arguments can be overridden.
+          '';
+        };
+        settings = mkOption {
+          type = with types; attrsOf (nullOr (oneOf [ bool str int pathInStore ]));
+          default = {};
+          description = lib.mdDoc ''
+            Settings to add as `module-arguments`.
+
+            Boolean values render just the key if true, and nothing if false. Null values are ignored. All other values are rendered as key-value pairs.
+          '';
+        };
+      };
+      config = {
+        inherit name;
+        # Formats an attrset of settings as args for use as `module-arguments`.
+        args = concatLists (flip mapAttrsToList config.settings (name: value:
+          if isBool value
+          then optional value name
+          else optional (value != null) "${name}=${toString value}"
+        ));
+      };
+    }));
+  };
+
   parentConfig = config;
 
   pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in {
@@ -18,6 +104,28 @@ let
         description = lib.mdDoc "Name of the PAM service.";
       };
 
+      rules = mkOption {
+        # This option is experimental and subject to breaking changes without notice.
+        visible = false;
+
+        description = lib.mdDoc ''
+          PAM rules for this service.
+
+          ::: {.warning}
+          This option and its suboptions are experimental and subject to breaking changes without notice.
+
+          If you use this option in your system configuration, you will need to manually monitor this module for any changes. Otherwise, failure to adjust your configuration properly could lead to you being locked out of your system, or worse, your system could be left wide open to attackers.
+
+          If you share configuration examples that use this option, you MUST include this warning so that users are informed.
+
+          You may freely use this option within `nixpkgs`, and future changes will account for those use sites.
+          :::
+        '';
+        type = types.submodule {
+          options = genAttrs [ "account" "auth" "password" "session" ] mkRulesTypeOption;
+        };
+      };
+
       unixAuth = mkOption {
         default = true;
         type = types.bool;
@@ -470,90 +578,114 @@ let
       setLoginUid = mkDefault cfg.startSession;
       limits = mkDefault config.security.pam.loginLimits;
 
+      text = let
+        ensureUniqueOrder = type: rules:
+          let
+            checkPair = a: b: assert assertMsg (a.order != b.order) "security.pam.services.${name}.rules.${type}: rules '${a.name}' and '${b.name}' cannot have the same order value (${toString a.order})"; b;
+            checked = zipListsWith checkPair rules (drop 1 rules);
+          in take 1 rules ++ checked;
+        # Formats a string for use in `module-arguments`. See `man pam.conf`.
+        formatModuleArgument = token:
+          if hasInfix " " token
+          then "[${replaceStrings ["]"] ["\\]"] token}]"
+          else token;
+        formatRules = type: pipe cfg.rules.${type} [
+          attrValues
+          (filter (rule: rule.enable))
+          (sort (a: b: a.order < b.order))
+          (ensureUniqueOrder type)
+          (map (rule: concatStringsSep " " (
+            [ type rule.control rule.modulePath ]
+            ++ map formatModuleArgument rule.args
+            ++ [ "# ${rule.name} (order ${toString rule.order})" ]
+          )))
+          (concatStringsSep "\n")
+        ];
+      in mkDefault ''
+        # Account management.
+        ${formatRules "account"}
+
+        # Authentication management.
+        ${formatRules "auth"}
+
+        # Password management.
+        ${formatRules "password"}
+
+        # Session management.
+        ${formatRules "session"}
+      '';
+
       # !!! TODO: move the LDAP stuff to the LDAP module, and the
       # Samba stuff to the Samba module.  This requires that the PAM
       # module provides the right hooks.
-      text = mkDefault
-        (
-          ''
-            # Account management.
-          '' +
-          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.kanidm.enablePam) ''
-            account sufficient ${pkgs.kanidm}/lib/pam_kanidm.so ignore_unknown_user
-          '' +
-          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.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
-          '' +
-          optionalString config.services.homed.enable ''
-            account sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
-          '' +
+      rules = let
+        autoOrderRules = flip pipe [
+          (imap1 (index: rule: rule // { order = mkDefault (10000 + index * 100); } ))
+          (map (rule: nameValuePair rule.name (removeAttrs rule [ "name" ])))
+          listToAttrs
+        ];
+      in {
+        account = autoOrderRules [
+          { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
+          { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
+            config_file = "/etc/security/pam_mysql.conf";
+          }; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; settings = {
+            ignore_unknown_user = true;
+          }; }
+          { name = "sss"; enable = config.services.sssd.enable; control = if cfg.sssdStrictAccess then "[default=bad success=ok user_unknown=ignore]" else "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
+          { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
+          { name = "oslogin_login"; enable = cfg.googleOsLoginAccountVerification; control = "[success=ok ignore=ignore default=die]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
+          { name = "oslogin_admin"; enable = cfg.googleOsLoginAccountVerification; control = "[success=ok default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so"; }
+          { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
           # The required pam_unix.so module has to come after all the sufficient modules
           # because otherwise, the account lookup will fail if the user does not exist
           # locally, for example with MySQL- or LDAP-auth.
-          ''
-            account required pam_unix.so
-
-            # Authentication management.
-          '' +
-          optionalString cfg.googleOsLoginAuthentication ''
-            auth [success=done perm_denied=die default=ignore] ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so
-          '' +
-          optionalString cfg.rootOK ''
-            auth sufficient pam_rootok.so
-          '' +
-          optionalString cfg.requireWheel ''
-            auth required pam_wheel.so use_uid
-          '' +
-          optionalString cfg.logFailures ''
-            auth required pam_faillock.so
-          '' +
-          optionalString 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}"} ${optionalString (u2f.origin != null) "origin=${u2f.origin}"}
-          '')) +
-          optionalString cfg.usbAuth ''
-            auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so
-          '' +
-          (let ussh = config.security.pam.ussh; in optionalString (config.security.pam.ussh.enable && cfg.usshAuth) ''
-            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.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"}
-          '') +
-          (let dp9ik = config.security.pam.dp9ik; in optionalString dp9ik.enable ''
-            auth ${dp9ik.control} ${pkgs.pam_dp9ik}/lib/security/pam_p9.so ${dp9ik.authserver}
-          '') +
-          optionalString cfg.fprintAuth ''
-            auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so
-          '' +
+          { name = "unix"; control = "required"; modulePath = "pam_unix.so"; }
+        ];
+
+        auth = autoOrderRules ([
+          { name = "oslogin_login"; enable = cfg.googleOsLoginAuthentication; control = "[success=done perm_denied=die default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
+          { name = "rootok"; enable = cfg.rootOK; control = "sufficient"; modulePath = "pam_rootok.so"; }
+          { name = "wheel"; enable = cfg.requireWheel; control = "required"; modulePath = "pam_wheel.so"; settings = {
+            use_uid = true;
+          }; }
+          { name = "faillock"; enable = cfg.logFailures; control = "required"; modulePath = "pam_faillock.so"; }
+          { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
+            config_file = "/etc/security/pam_mysql.conf";
+          }; }
+          { name = "ssh_agent_auth"; enable = config.security.pam.sshAgentAuth.enable && cfg.sshAgentAuth; control = "sufficient"; modulePath = "${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so"; settings = {
+            file = lib.concatStringsSep ":" config.security.pam.sshAgentAuth.authorizedKeysFiles;
+          }; }
+          (let p11 = config.security.pam.p11; in { name = "p11"; enable = cfg.p11Auth; control = p11.control; modulePath = "${pkgs.pam_p11}/lib/security/pam_p11.so"; args = [
+            "${pkgs.opensc}/lib/opensc-pkcs11.so"
+          ]; })
+          (let u2f = config.security.pam.u2f; in { name = "u2f"; enable = cfg.u2fAuth; control = u2f.control; modulePath = "${pkgs.pam_u2f}/lib/security/pam_u2f.so"; settings = {
+            inherit (u2f) debug interactive cue origin;
+            authfile = u2f.authFile;
+            appid = u2f.appId;
+          }; })
+          { name = "usb"; enable = cfg.usbAuth; control = "sufficient"; modulePath = "${pkgs.pam_usb}/lib/security/pam_usb.so"; }
+          (let ussh = config.security.pam.ussh; in { name = "ussh"; enable = config.security.pam.ussh.enable && cfg.usshAuth; control = ussh.control; modulePath = "${pkgs.pam_ussh}/lib/security/pam_ussh.so"; settings = {
+            ca_file = ussh.caFile;
+            authorized_principals = ussh.authorizedPrincipals;
+            authorized_principals_file = ussh.authorizedPrincipalsFile;
+            inherit (ussh) group;
+          }; })
+          (let oath = config.security.pam.oath; in { name = "oath"; enable = cfg.oathAuth; control = "requisite"; modulePath = "${pkgs.oath-toolkit}/lib/security/pam_oath.so"; settings = {
+            inherit (oath) window digits;
+            usersfile = oath.usersFile;
+          }; })
+          (let yubi = config.security.pam.yubico; in { name = "yubico"; enable = cfg.yubicoAuth; control = yubi.control; modulePath = "${pkgs.yubico-pam}/lib/security/pam_yubico.so"; settings = {
+            inherit (yubi) mode debug;
+            chalresp_path = yubi.challengeResponsePath;
+            id = mkIf (yubi.mode == "client") yubi.id;
+          }; })
+          (let dp9ik = config.security.pam.dp9ik; in { name = "p9"; enable = dp9ik.enable; control = dp9ik.control; modulePath = "${pkgs.pam_dp9ik}/lib/security/pam_p9.so"; args = [
+            dp9ik.authserver
+          ]; })
+          { name = "fprintd"; enable = cfg.fprintAuth; control = "sufficient"; modulePath = "${pkgs.fprintd}/lib/security/pam_fprintd.so"; }
+        ] ++
           # Modules in this block require having the password set in PAM_AUTHTOK.
           # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
           # after it succeeds. Certain modules need to run after pam_unix
@@ -562,7 +694,7 @@ let
           # We use try_first_pass the second time to avoid prompting password twice.
           #
           # The same principle applies to systemd-homed
-          (optionalString ((cfg.unixAuth || config.services.homed.enable) &&
+          (optionals ((cfg.unixAuth || config.services.homed.enable) &&
             (config.security.pam.enableEcryptfs
               || config.security.pam.enableFscrypt
               || cfg.pamMount
@@ -573,199 +705,173 @@ let
               || cfg.failDelay.enable
               || cfg.duoSecurity.enable
               || cfg.zfs))
-            (
-              optionalString config.services.homed.enable ''
-                auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so
-              '' +
-              optionalString cfg.unixAuth ''
-                auth optional pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth
-              '' +
-              optionalString config.security.pam.enableEcryptfs ''
-                auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap
-              '' +
-              optionalString config.security.pam.enableFscrypt ''
-                auth optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
-              '' +
-              optionalString cfg.zfs ''
-                auth optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes}
-              '' +
-              optionalString cfg.pamMount ''
-                auth optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
-              '' +
-              optionalString cfg.enableKwallet ''
-               auth optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5
-              '' +
-              optionalString cfg.enableGnomeKeyring ''
-                auth optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so
-              '' +
-              optionalString cfg.gnupg.enable ''
-                auth optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.storeOnly " store-only"}
-              '' +
-              optionalString cfg.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
-              '' +
-              optionalString cfg.duoSecurity.enable ''
-                auth required ${pkgs.duo-unix}/lib/security/pam_duo.so
-              ''
-            )) +
-          optionalString config.services.homed.enable ''
-            auth sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
-          '' +
-          optionalString cfg.unixAuth ''
-            auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} ${optionalString cfg.nodelay "nodelay"} likeauth try_first_pass
-          '' +
-          optionalString cfg.otpwAuth ''
-            auth sufficient ${pkgs.otpw}/lib/security/pam_otpw.so
-          '' +
-          optionalString use_ldap ''
-            auth sufficient ${pam_ldap}/lib/security/pam_ldap.so use_first_pass
-          '' +
-          optionalString config.services.kanidm.enablePam ''
-            auth sufficient ${pkgs.kanidm}/lib/pam_kanidm.so ignore_unknown_user use_first_pass
-          '' +
-          optionalString config.services.sssd.enable ''
-            auth sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_first_pass
-          '' +
-          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
-          '' +
-          ''
-            auth required pam_deny.so
-
-            # Password management.
-          '' +
-          optionalString config.services.homed.enable ''
-            password sufficient ${config.systemd.package}/lib/security/pam_systemd_home.so
-          '' + ''
-            password sufficient pam_unix.so nullok yescrypt
-          '' +
-          optionalString config.security.pam.enableEcryptfs ''
-            password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
-          '' +
-          optionalString config.security.pam.enableFscrypt ''
-            password optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
-          '' +
-          optionalString cfg.zfs ''
-            password optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes}
-          '' +
-          optionalString cfg.pamMount ''
-            password optional ${pkgs.pam_mount}/lib/security/pam_mount.so
-          '' +
-          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.kanidm.enablePam ''
-            password sufficient ${pkgs.kanidm}/lib/pam_kanidm.so
-          '' +
-          optionalString config.services.sssd.enable ''
-            password sufficient ${pkgs.sssd}/lib/security/pam_sss.so
-          '' +
-          optionalString config.security.pam.krb5.enable ''
-            password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
-          '' +
-          optionalString cfg.enableGnomeKeyring ''
-            password optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so use_authtok
-          '' +
-          ''
+            [
+              { name = "systemd_home-early"; enable = config.services.homed.enable; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
+              { name = "unix-early"; enable = cfg.unixAuth; control = "optional"; modulePath = "pam_unix.so"; settings = {
+                nullok = cfg.allowNullPassword;
+                inherit (cfg) nodelay;
+                likeauth = true;
+              }; }
+              { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; settings = {
+                unwrap = true;
+              }; }
+              { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
+              { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
+                inherit (config.security.pam.zfs) homes;
+              }; }
+              { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
+                disable_interactive = true;
+              }; }
+              { name = "kwallet5"; enable = cfg.enableKwallet; control = "optional"; modulePath = "${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so"; settings = {
+                kwalletd = "${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5";
+              }; }
+              { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; }
+              { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
+                store-only = cfg.gnupg.storeOnly;
+              }; }
+              { name = "faildelay"; enable = cfg.failDelay.enable; control = "optional"; modulePath = "${pkgs.pam}/lib/security/pam_faildelay.so"; settings = {
+                inherit (cfg.failDelay) delay;
+              }; }
+              { name = "google_authenticator"; enable = cfg.googleAuthenticator.enable; control = "required"; modulePath = "${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so"; settings = {
+                no_increment_hotp = true;
+              }; }
+              { name = "duo"; enable = cfg.duoSecurity.enable; control = "required"; modulePath = "${pkgs.duo-unix}/lib/security/pam_duo.so"; }
+            ]) ++ [
+          { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
+          { name = "unix"; enable = cfg.unixAuth; control = "sufficient"; modulePath = "pam_unix.so"; settings = {
+            nullok = cfg.allowNullPassword;
+            inherit (cfg) nodelay;
+            likeauth = true;
+            try_first_pass = true;
+          }; }
+          { name = "otpw"; enable = cfg.otpwAuth; control = "sufficient"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
+          { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; settings = {
+            use_first_pass = true;
+          }; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; settings = {
+            ignore_unknown_user = true;
+            use_first_pass = true;
+          }; }
+          { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; settings = {
+            use_first_pass = true;
+          }; }
+          { name = "krb5"; enable = config.security.pam.krb5.enable; control = "[default=ignore success=1 service_err=reset]"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; settings = {
+            use_first_pass = true;
+          }; }
+          { name = "ccreds-validate"; enable = config.security.pam.krb5.enable; control = "[default=die success=done]"; modulePath = "${pam_ccreds}/lib/security/pam_ccreds.so"; settings = {
+            action = "validate";
+            use_first_pass = true;
+          }; }
+          { name = "ccreds-store"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_ccreds}/lib/security/pam_ccreds.so"; settings = {
+            action = "store";
+            use_first_pass = true;
+          }; }
+          { name = "deny"; control = "required"; modulePath = "pam_deny.so"; }
+        ]);
+
+        password = autoOrderRules [
+          { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
+          { name = "unix"; control = "sufficient"; modulePath = "pam_unix.so"; settings = {
+            nullok = true;
+            yescrypt = true;
+          }; }
+          { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
+          { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
+          { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
+            inherit (config.security.pam.zfs) homes;
+          }; }
+          { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; }
+          { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
+          { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
+            config_file = "/etc/security/pam_mysql.conf";
+          }; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; }
+          { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
+          { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; settings = {
+            use_first_pass = true;
+          }; }
+          { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
+            use_authtok = true;
+          }; }
+        ];
 
-            # Session management.
-          '' +
-          optionalString cfg.setEnvironment ''
-            session required pam_env.so conffile=/etc/pam/environment readenv=0
-          '' +
-          ''
-            session required pam_unix.so
-          '' +
-          optionalString cfg.setLoginUid ''
-            session ${if config.boot.isContainer then "optional" else "required"} pam_loginuid.so
-          '' +
-          optionalString cfg.ttyAudit.enable (concatStringsSep " \\\n  " ([
-            "session required ${pkgs.pam}/lib/security/pam_tty_audit.so"
-          ] ++ optional cfg.ttyAudit.openOnly "open_only"
-          ++ optional (cfg.ttyAudit.enablePattern != null) "enable=${cfg.ttyAudit.enablePattern}"
-          ++ optional (cfg.ttyAudit.disablePattern != null) "disable=${cfg.ttyAudit.disablePattern}"
-          )) +
-          optionalString config.services.homed.enable ''
-            session required ${config.systemd.package}/lib/security/pam_systemd_home.so
-          '' +
-          optionalString cfg.makeHomeDir ''
-            session required ${pkgs.pam}/lib/security/pam_mkhomedir.so silent skel=${config.security.pam.makeHomeDir.skelDirectory} umask=${config.security.pam.makeHomeDir.umask}
-          '' +
-          optionalString cfg.updateWtmp ''
-            session required ${pkgs.pam}/lib/security/pam_lastlog.so silent
-          '' +
-          optionalString config.security.pam.enableEcryptfs ''
-            session optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so
-          '' +
-          optionalString config.security.pam.enableFscrypt ''
-            # Work around https://github.com/systemd/systemd/issues/8598
-            # Skips the pam_fscrypt module for systemd-user sessions which do not have a password
-            # anyways.
-            # See also https://github.com/google/fscrypt/issues/95
-            session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
-            session optional ${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so
-          '' +
-          optionalString cfg.zfs ''
-            session [success=1 default=ignore] pam_succeed_if.so service = systemd-user
-            session optional ${config.boot.zfs.package}/lib/security/pam_zfs_key.so homes=${config.security.pam.zfs.homes} ${optionalString config.security.pam.zfs.noUnmount "nounmount"}
-          '' +
-          optionalString cfg.pamMount ''
-            session optional ${pkgs.pam_mount}/lib/security/pam_mount.so disable_interactive
-          '' +
-          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.kanidm.enablePam ''
-            session optional ${pkgs.kanidm}/lib/pam_kanidm.so
-          '' +
-          optionalString config.services.sssd.enable ''
-            session optional ${pkgs.sssd}/lib/security/pam_sss.so
-          '' +
-          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 ${config.systemd.package}/lib/security/pam_systemd.so
-          '' +
-          optionalString cfg.forwardXAuth ''
-            session optional pam_xauth.so xauthpath=${pkgs.xorg.xauth}/bin/xauth systemuser=99
-          '' +
-          optionalString (cfg.limits != []) ''
-            session required ${pkgs.pam}/lib/security/pam_limits.so conf=${makeLimitsConf cfg.limits}
-          '' +
-          optionalString (cfg.showMotd && (config.users.motd != null || config.users.motdFile != null)) ''
-            session optional ${pkgs.pam}/lib/security/pam_motd.so motd=${motd}
-          '' +
-          optionalString (cfg.enableAppArmor && config.security.apparmor.enable) ''
-            session optional ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so order=user,group,default debug
-          '' +
-          optionalString (cfg.enableKwallet) ''
-            session optional ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so kwalletd=${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5
-          '' +
-          optionalString (cfg.enableGnomeKeyring) ''
-            session optional ${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start
-          '' +
-          optionalString cfg.gnupg.enable ''
-            session optional ${pkgs.pam_gnupg}/lib/security/pam_gnupg.so ${optionalString cfg.gnupg.noAutostart " no-autostart"}
-          '' +
-          optionalString (config.virtualisation.lxc.lxcfs.enable) ''
-            session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all
-          ''
-        );
+        session = autoOrderRules [
+          { name = "env"; enable = cfg.setEnvironment; control = "required"; modulePath = "pam_env.so"; settings = {
+            conffile = "/etc/pam/environment";
+            readenv = 0;
+          }; }
+          { name = "unix"; control = "required"; modulePath = "pam_unix.so"; }
+          { name = "loginuid"; enable = cfg.setLoginUid; control = if config.boot.isContainer then "optional" else "required"; modulePath = "pam_loginuid.so"; }
+          { name = "tty_audit"; enable = cfg.ttyAudit.enable; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_tty_audit.so"; settings = {
+            open_only = cfg.ttyAudit.openOnly;
+            enable = cfg.ttyAudit.enablePattern;
+            disable = cfg.ttyAudit.disablePattern;
+          }; }
+          { name = "systemd_home"; enable = config.services.homed.enable; control = "required"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
+          { name = "mkhomedir"; enable = cfg.makeHomeDir; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_mkhomedir.so"; settings = {
+            silent = true;
+            skel = config.security.pam.makeHomeDir.skelDirectory;
+            inherit (config.security.pam.makeHomeDir) umask;
+          }; }
+          { name = "lastlog"; enable = cfg.updateWtmp; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_lastlog.so"; settings = {
+            silent = true;
+          }; }
+          { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
+          # 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
+          { name = "fscrypt-skip-systemd"; enable = config.security.pam.enableFscrypt; control = "[success=1 default=ignore]"; modulePath = "pam_succeed_if.so"; args = [
+            "service" "=" "systemd-user"
+          ]; }
+          { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
+          { name = "zfs_key-skip-systemd"; enable = cfg.zfs; control = "[success=1 default=ignore]"; modulePath = "pam_succeed_if.so"; args = [
+            "service" "=" "systemd-user"
+          ]; }
+          { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
+            inherit (config.security.pam.zfs) homes;
+            nounmount = config.security.pam.zfs.noUnmount;
+          }; }
+          { name = "mount"; enable = cfg.pamMount; control = "optional"; modulePath = "${pkgs.pam_mount}/lib/security/pam_mount.so"; settings = {
+            disable_interactive = true;
+          }; }
+          { name = "ldap"; enable = use_ldap; control = "optional"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; }
+          { name = "mysql"; enable = cfg.mysqlAuth; control = "optional"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
+            config_file = "/etc/security/pam_mysql.conf";
+          }; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "optional"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; }
+          { name = "sss"; enable = config.services.sssd.enable; control = "optional"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
+          { name = "krb5"; enable = config.security.pam.krb5.enable; control = "optional"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
+          { name = "otpw"; enable = cfg.otpwAuth; control = "optional"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
+          { name = "systemd"; enable = cfg.startSession; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd.so"; }
+          { name = "xauth"; enable = cfg.forwardXAuth; control = "optional"; modulePath = "pam_xauth.so"; settings = {
+            xauthpath = "${pkgs.xorg.xauth}/bin/xauth";
+            systemuser = 99;
+          }; }
+          { name = "limits"; enable = cfg.limits != []; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_limits.so"; settings = {
+            conf = "${makeLimitsConf cfg.limits}";
+          }; }
+          { name = "motd"; enable = cfg.showMotd && (config.users.motd != null || config.users.motdFile != null); control = "optional"; modulePath = "${pkgs.pam}/lib/security/pam_motd.so"; settings = {
+            inherit motd;
+          }; }
+          { name = "apparmor"; enable = cfg.enableAppArmor && config.security.apparmor.enable; control = "optional"; modulePath = "${pkgs.apparmor-pam}/lib/security/pam_apparmor.so"; settings = {
+            order = "user,group,default";
+            debug = true;
+          }; }
+          { name = "kwallet5"; enable = cfg.enableKwallet; control = "optional"; modulePath = "${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so"; settings = {
+            kwalletd = "${pkgs.plasma5Packages.kwallet.bin}/bin/kwalletd5";
+          }; }
+          { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
+            auto_start = true;
+          }; }
+          { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
+            no-autostart = cfg.gnupg.noAutostart;
+          }; }
+          { name = "cgfs"; enable = config.virtualisation.lxc.lxcfs.enable; control = "optional"; modulePath = "${pkgs.lxc}/lib/security/pam_cgfs.so"; args = [
+            "-c" "all"
+          ]; }
+        ];
+      };
     };
 
   };
@@ -837,12 +943,20 @@ let
       value.source = pkgs.writeText "${name}.pam" service.text;
     };
 
+  optionalSudoConfigForSSHAgentAuth = optionalString config.security.pam.sshAgentAuth.enable ''
+    # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
+    Defaults env_keep+=SSH_AUTH_SOCK
+  '';
+
 in
 
 {
 
+  meta.maintainers = [ maintainers.majiir ];
+
   imports = [
     (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
+    (mkRenamedOptionModule [ "security" "pam" "enableSSHAgentAuth" ] [ "security" "pam" "sshAgentAuth" "enable" ])
   ];
 
   ###### interface
@@ -912,16 +1026,34 @@ in
       '';
     };
 
-    security.pam.enableSSHAgentAuth = mkOption {
-      type = types.bool;
-      default = false;
-      description =
-        lib.mdDoc ''
-          Enable sudo logins if the user's SSH agent provides a key
-          present in {file}`~/.ssh/authorized_keys`.
-          This allows machines to exclusively use SSH keys instead of
-          passwords.
+    security.pam.sshAgentAuth = {
+      enable = mkEnableOption ''
+        authenticating using a signature performed by the ssh-agent.
+        This allows using SSH keys exclusively, instead of passwords, for instance on remote machines
+      '';
+
+      authorizedKeysFiles = mkOption {
+        type = with types; listOf str;
+        description = ''
+          A list of paths to files in OpenSSH's `authorized_keys` format, containing
+          the keys that will be trusted by the `pam_ssh_agent_auth` module.
+
+          The following patterns are expanded when interpreting the path:
+          - `%f` and `%H` respectively expand to the fully-qualified and short hostname ;
+          - `%u` expands to the username ;
+          - `~` or `%h` expands to the user's home directory.
+
+          ::: {.note}
+          Specifying user-writeable files here result in an insecure configuration:  a malicious process
+          can then edit such an authorized_keys file and bypass the ssh-agent-based authentication.
+
+          See [issue #31611](https://github.com/NixOS/nixpkgs/issues/31611)
+          :::
         '';
+        example = [ "/etc/ssh/authorized_keys.d/%u" ];
+        default = config.services.openssh.authorizedKeysFiles;
+        defaultText = literalExpression "config.services.openssh.authorizedKeysFiles";
+      };
     };
 
     security.pam.enableOTPW = mkEnableOption (lib.mdDoc "the OTPW (one-time password) PAM module");
@@ -954,8 +1086,8 @@ in
 
     security.pam.krb5 = {
       enable = mkOption {
-        default = config.krb5.enable;
-        defaultText = literalExpression "config.krb5.enable";
+        default = config.security.krb5.enable;
+        defaultText = literalExpression "config.security.krb5.enable";
         type = types.bool;
         description = lib.mdDoc ''
           Enables Kerberos PAM modules (`pam-krb5`,
@@ -963,7 +1095,7 @@ in
 
           If set, users can authenticate with their Kerberos password.
           This requires a valid Kerberos configuration
-          (`config.krb5.enable` should be set to
+          (`config.security.krb5.enable` should be set to
           `true`).
 
           Note that the Kerberos PAM modules are not necessary when using SSS
@@ -1303,7 +1435,7 @@ in
 
     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.
+      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
@@ -1343,8 +1475,25 @@ in
           `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled` or `boot.zfs.enableUnstable`).
         '';
       }
+      {
+        assertion = with config.security.pam.sshAgentAuth; enable -> authorizedKeysFiles != [];
+        message = ''
+          `security.pam.enableSSHAgentAuth` requires `services.openssh.authorizedKeysFiles` to be a non-empty list.
+          Did you forget to set `services.openssh.enable` ?
+        '';
+      }
     ];
 
+    warnings = optional
+      (with lib; with config.security.pam.sshAgentAuth;
+        enable && any (s: hasPrefix "%h" s || hasPrefix "~" s) authorizedKeysFiles)
+      ''config.security.pam.sshAgentAuth.authorizedKeysFiles contains files in the user's home directory.
+
+        Specifying user-writeable files there result in an insecure configuration:
+        a malicious process can then edit such an authorized_keys file and bypass the ssh-agent-based authentication.
+        See https://github.com/NixOS/nixpkgs/issues/31611
+      '';
+
     environment.systemPackages =
       # Include the PAM modules in the system path mostly for the manpages.
       [ pkgs.pam ]
@@ -1402,9 +1551,7 @@ in
         fscrypt = {};
       };
 
-    security.apparmor.includes."abstractions/pam" = let
-      isEnabled = test: fold or false (map test (attrValues config.security.pam.services));
-      in
+    security.apparmor.includes."abstractions/pam" =
       lib.concatMapStrings
         (name: "r ${config.environment.etc."pam.d/${name}".source},\n")
         (attrNames config.security.pam.services) +
@@ -1413,88 +1560,20 @@ in
       mr ${getLib pkgs.pam}/lib/security/pam_*.so,
       r ${getLib pkgs.pam}/lib/security/,
       '' +
-      optionalString use_ldap ''
-         mr ${pam_ldap}/lib/security/pam_ldap.so,
-      '' +
-      optionalString config.services.kanidm.enablePam ''
-        mr ${pkgs.kanidm}/lib/pam_kanidm.so,
-      '' +
-      optionalString config.services.sssd.enable ''
-        mr ${pkgs.sssd}/lib/security/pam_sss.so,
-      '' +
-      optionalString config.security.pam.krb5.enable ''
-        mr ${pam_krb5}/lib/security/pam_krb5.so,
-        mr ${pam_ccreds}/lib/security/pam_ccreds.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.googleOsLoginAccountVerification)) ''
-        mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so,
-        mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_admin.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.googleOsLoginAuthentication)) ''
-        mr ${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so,
-      '' +
-      optionalString (config.security.pam.enableSSHAgentAuth
-                     && isEnabled (cfg: cfg.sshAgentAuth)) ''
-        mr ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.fprintAuth)) ''
-        mr ${pkgs.fprintd}/lib/security/pam_fprintd.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.u2fAuth)) ''
-        mr ${pkgs.pam_u2f}/lib/security/pam_u2f.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.usbAuth)) ''
-        mr ${pkgs.pam_usb}/lib/security/pam_usb.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.usshAuth)) ''
-        mr ${pkgs.pam_ussh}/lib/security/pam_ussh.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.oathAuth)) ''
-        "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,
-      '' +
-      optionalString (isEnabled (cfg: cfg.duoSecurity.enable)) ''
-        mr ${pkgs.duo-unix}/lib/security/pam_duo.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.otpwAuth)) ''
-        mr ${pkgs.otpw}/lib/security/pam_otpw.so,
-      '' +
-      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.gnome.gnome-keyring}/lib/security/pam_gnome_keyring.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.startSession)) ''
-        mr ${config.systemd.package}/lib/security/pam_systemd.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.enableAppArmor)
-                     && config.security.apparmor.enable) ''
-        mr ${pkgs.apparmor-pam}/lib/security/pam_apparmor.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.enableKwallet)) ''
-        mr ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so,
-      '' +
-      optionalString config.virtualisation.lxc.lxcfs.enable ''
-        mr ${pkgs.lxc}/lib/security/pam_cgfs.so,
-      '' +
-      optionalString (isEnabled (cfg: cfg.zfs)) ''
-        mr ${config.boot.zfs.package}/lib/security/pam_zfs_key.so,
-      '' +
-      optionalString config.services.homed.enable ''
-        mr ${config.systemd.package}/lib/security/pam_systemd_home.so
-      '';
+      (with lib; pipe config.security.pam.services [
+        attrValues
+        (catAttrs "rules")
+        (concatMap attrValues)
+        (concatMap attrValues)
+        (filter (rule: rule.enable))
+        (catAttrs "modulePath")
+        (filter (hasPrefix "/"))
+        unique
+        (map (module: "mr ${module},"))
+        concatLines
+      ]);
+
+    security.sudo.extraConfig = optionalSudoConfigForSSHAgentAuth;
+    security.sudo-rs.extraConfig = optionalSudoConfigForSSHAgentAuth;
   };
-
 }
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index ad78f38b08663..26f906f2a76af 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -33,7 +33,7 @@ in
         default = [];
         description = lib.mdDoc ''
           List of volume definitions for pam_mount.
-          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
@@ -78,7 +78,7 @@ in
         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 <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
@@ -88,7 +88,7 @@ in
         description = lib.mdDoc ''
           Amount of microseconds to wait until killing remaining processes after
           final logout.
-          For more information, visit <http://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
+          For more information, visit <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>.
         '';
       };
 
diff --git a/nixos/modules/security/please.nix b/nixos/modules/security/please.nix
index 88bb9cba2bfc0..ff4bfc9f1be1a 100644
--- a/nixos/modules/security/please.nix
+++ b/nixos/modules/security/please.nix
@@ -13,14 +13,7 @@ in
       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`.
-      '';
-    };
+    package = mkPackageOption pkgs "please" { };
 
     wheelNeedsPassword = mkOption {
       type = types.bool;
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index de427ccb295bb..327f49c0b6378 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -35,7 +35,7 @@ in
       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
+          <https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html#polkit-rules>
         '';
     };
 
@@ -117,4 +117,3 @@ in
   };
 
 }
-
diff --git a/nixos/modules/security/sudo-rs.nix b/nixos/modules/security/sudo-rs.nix
new file mode 100644
index 0000000000000..b4376562c34d7
--- /dev/null
+++ b/nixos/modules/security/sudo-rs.nix
@@ -0,0 +1,269 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.security.sudo-rs;
+
+  toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
+  toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
+
+  toCommandOptionsString = options:
+    "${concatStringsSep ":" options}${optionalString (length options != 0) ":"} ";
+
+  toCommandsString = commands:
+    concatStringsSep ", " (
+      map (command:
+        if (isString command) then
+          command
+        else
+          "${toCommandOptionsString command.options}${command.command}"
+      ) commands
+    );
+
+in
+
+{
+
+  ###### interface
+
+  options.security.sudo-rs = {
+
+    defaultOptions = mkOption {
+      type = with types; listOf str;
+      default = [];
+      description = mdDoc ''
+        Options used for the default rules, granting `root` and the
+        `wheel` group permission to run any command as any user.
+      '';
+    };
+
+    enable = mkEnableOption (mdDoc ''
+      a memory-safe implementation of the {command}`sudo` command,
+      which allows non-root users to execute commands as root.
+    '');
+
+    package = mkPackageOption pkgs "sudo-rs" { };
+
+    wheelNeedsPassword = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc ''
+        Whether users of the `wheel` group must
+        provide a password to run commands as super user via {command}`sudo`.
+      '';
+      };
+
+    execWheelOnly = mkOption {
+      type = types.bool;
+      default = false;
+      description = 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 `wheel` from
+        exploiting vulnerabilities in sudo such as CVE-2021-3156.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.lines;
+      # Note: if syntax errors are detected in this file, the NixOS
+      # configuration will fail to build.
+      description = mdDoc ''
+        This string contains the contents of the
+        {file}`sudoers` file.
+      '';
+    };
+
+    extraRules = mkOption {
+      description = 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.
+      '';
+      default = [];
+      example = literalExpression ''
+        [
+          # Allow execution of any command by all users in group sudo,
+          # requiring a password.
+          { groups = [ "sudo" ]; commands = [ "ALL" ]; }
+
+          # Allow execution of "/home/root/secret.sh" by user `backup`, `database`
+          # and the group with GID `1006` without a password.
+          { users = [ "backup" "database" ]; groups = [ 1006 ];
+            commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; }
+
+          # Allow all users of group `bar` to run two executables as user `foo`
+          # with arguments being pre-set.
+          { groups = [ "bar" ]; runAs = "foo";
+            commands =
+              [ "/home/baz/cmd1.sh hello-sudo"
+                  { command = '''/home/baz/cmd2.sh ""'''; options = [ "SETENV" ]; } ]; }
+        ]
+      '';
+      type = with types; listOf (submodule {
+        options = {
+          users = mkOption {
+            type = with types; listOf (either str int);
+            description = mdDoc ''
+              The usernames / UIDs this rule should apply for.
+            '';
+            default = [];
+          };
+
+          groups = mkOption {
+            type = with types; listOf (either str int);
+            description = mdDoc ''
+              The groups / GIDs this rule should apply for.
+            '';
+            default = [];
+          };
+
+          host = mkOption {
+            type = types.str;
+            default = "ALL";
+            description = mdDoc ''
+              For what host this rule should apply.
+            '';
+          };
+
+          runAs = mkOption {
+            type = with types; str;
+            default = "ALL:ALL";
+            description = mdDoc ''
+              Under which user/group the specified command is allowed to run.
+
+              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 = mdDoc ''
+              The commands for which the rule should apply.
+            '';
+            type = with types; listOf (either str (submodule {
+
+              options = {
+                command = mkOption {
+                  type = with types; str;
+                  description = mdDoc ''
+                    A command being either just a path to a binary to allow any arguments,
+                    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 = mdDoc ''
+                    Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
+                  '';
+                  default = [];
+                };
+              };
+
+            }));
+          };
+        };
+      });
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = mdDoc ''
+        Extra configuration text appended to {file}`sudoers`.
+      '';
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    assertions = [ {
+      assertion = ! config.security.sudo.enable;
+      message = "`security.sudo` and `security.sudo-rs` cannot both be enabled";
+    }];
+    security.sudo.enable = mkDefault false;
+
+    security.sudo-rs.extraRules =
+      let
+        defaultRule = { users ? [], groups ? [], opts ? [] }: [ {
+          inherit users groups;
+          commands = [ {
+            command = "ALL";
+            options = opts ++ cfg.defaultOptions;
+          } ];
+        } ];
+      in mkMerge [
+        # This is ordered before users' `mkBefore` rules,
+        # so as not to introduce unexpected changes.
+        (mkOrder 400 (defaultRule { users = [ "root" ]; }))
+
+        # This is ordered to show before (most) other rules, but
+        # late-enough for a user to `mkBefore` it.
+        (mkOrder 600 (defaultRule {
+          groups = [ "wheel" ];
+          opts = (optional (!cfg.wheelNeedsPassword) "NOPASSWD");
+        }))
+      ];
+
+    security.sudo-rs.configFile = concatStringsSep "\n" (filter (s: s != "") [
+      ''
+        # Don't edit this file. Set the NixOS options ‘security.sudo-rs.configFile’
+        # or ‘security.sudo-rs.extraRules’ instead.
+      ''
+      (pipe cfg.extraRules [
+        (filter (rule: length rule.commands != 0))
+        (map (rule: [
+          (map (user: "${toUserString user}     ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.users)
+          (map (group: "${toGroupString group}  ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.groups)
+        ]))
+        flatten
+        (concatStringsSep "\n")
+      ])
+      "\n"
+      (optionalString (cfg.extraConfig != "") ''
+        # extraConfig
+        ${cfg.extraConfig}
+      '')
+    ]);
+
+    security.wrappers = let
+      owner = "root";
+      group = if cfg.execWheelOnly then "wheel" else "root";
+      setuid = true;
+      permissions = if cfg.execWheelOnly then "u+rx,g+x" else "u+rx,g+x,o+x";
+    in {
+      sudo = {
+        source = "${cfg.package.out}/bin/sudo";
+        inherit owner group setuid permissions;
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
+    security.pam.services.sudo-i = { sshAgentAuth = true; usshAuth = true; };
+
+    environment.etc.sudoers =
+      { source =
+          pkgs.runCommand "sudoers"
+          {
+            src = pkgs.writeText "sudoers-in" cfg.configFile;
+            preferLocalBuild = true;
+          }
+          "${pkgs.buildPackages.sudo-rs}/bin/visudo -f $src -c && cp $src $out";
+        mode = "0440";
+      };
+
+  };
+
+  meta.maintainers = [ lib.maintainers.nicoo ];
+
+}
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index 9ac91bd0d3686..6aa9445eab65e 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -6,8 +6,6 @@ let
 
   cfg = config.security.sudo;
 
-  inherit (pkgs) sudo;
-
   toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
   toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
 
@@ -30,9 +28,18 @@ in
 
   ###### interface
 
-  options = {
+  options.security.sudo = {
+
+    defaultOptions = mkOption {
+      type = with types; listOf str;
+      default = [ "SETENV" ];
+      description = mdDoc ''
+        Options used for the default rules, granting `root` and the
+        `wheel` group permission to run any command as any user.
+      '';
+    };
 
-    security.sudo.enable = mkOption {
+    enable = mkOption {
       type = types.bool;
       default = true;
       description =
@@ -42,29 +49,21 @@ in
         '';
     };
 
-    security.sudo.package = mkOption {
-      type = types.package;
-      default = pkgs.sudo;
-      defaultText = literalExpression "pkgs.sudo";
-      description = lib.mdDoc ''
-        Which package to use for `sudo`.
-      '';
-    };
+    package = mkPackageOption pkgs "sudo" { };
 
-    security.sudo.wheelNeedsPassword = mkOption {
+    wheelNeedsPassword = mkOption {
       type = types.bool;
       default = true;
-      description =
-        lib.mdDoc ''
-          Whether users of the `wheel` group must
-          provide a password to run commands as super user via {command}`sudo`.
-        '';
+      description = mdDoc ''
+        Whether users of the `wheel` group must
+        provide a password to run commands as super user via {command}`sudo`.
+      '';
       };
 
-    security.sudo.execWheelOnly = mkOption {
+    execWheelOnly = mkOption {
       type = types.bool;
       default = false;
-      description = lib.mdDoc ''
+      description = 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 `wheel` from
@@ -72,19 +71,18 @@ in
       '';
     };
 
-    security.sudo.configFile = mkOption {
+    configFile = mkOption {
       type = types.lines;
       # 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
-          {file}`sudoers` file.
-        '';
+      description = mdDoc ''
+        This string contains the contents of the
+        {file}`sudoers` file.
+      '';
     };
 
-    security.sudo.extraRules = mkOption {
-      description = lib.mdDoc ''
+    extraRules = mkOption {
+      description = 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
@@ -114,7 +112,7 @@ in
         options = {
           users = mkOption {
             type = with types; listOf (either str int);
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The usernames / UIDs this rule should apply for.
             '';
             default = [];
@@ -122,7 +120,7 @@ in
 
           groups = mkOption {
             type = with types; listOf (either str int);
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The groups / GIDs this rule should apply for.
             '';
             default = [];
@@ -131,7 +129,7 @@ in
           host = mkOption {
             type = types.str;
             default = "ALL";
-            description = lib.mdDoc ''
+            description = mdDoc ''
               For what host this rule should apply.
             '';
           };
@@ -139,7 +137,7 @@ in
           runAs = mkOption {
             type = with types; str;
             default = "ALL:ALL";
-            description = lib.mdDoc ''
+            description = mdDoc ''
               Under which user/group the specified command is allowed to run.
 
               A user can be specified using just the username: `"foo"`.
@@ -149,7 +147,7 @@ in
           };
 
           commands = mkOption {
-            description = lib.mdDoc ''
+            description = mdDoc ''
               The commands for which the rule should apply.
             '';
             type = with types; listOf (either str (submodule {
@@ -157,7 +155,7 @@ in
               options = {
                 command = mkOption {
                   type = with types; str;
-                  description = lib.mdDoc ''
+                  description = mdDoc ''
                     A command being either just a path to a binary to allow any arguments,
                     the full command with arguments pre-set or with `""` used as the argument,
                     not allowing arguments to the command at all.
@@ -166,7 +164,7 @@ in
 
                 options = mkOption {
                   type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
-                  description = lib.mdDoc ''
+                  description = mdDoc ''
                     Options for running the command. Refer to the [sudo manual](https://www.sudo.ws/man/1.7.10/sudoers.man.html).
                   '';
                   default = [];
@@ -179,10 +177,10 @@ in
       });
     };
 
-    security.sudo.extraConfig = mkOption {
+    extraConfig = mkOption {
       type = types.lines;
       default = "";
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Extra configuration text appended to {file}`sudoers`.
       '';
     };
@@ -192,40 +190,55 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-
-    # We `mkOrder 600` so that the default rule shows up first, but there is
-    # still enough room for a user to `mkBefore` it.
-    security.sudo.extraRules = mkOrder 600 [
-      { groups = [ "wheel" ];
-        commands = [ { command = "ALL"; options = (if cfg.wheelNeedsPassword then [ "SETENV" ] else [ "NOPASSWD" "SETENV" ]); } ];
-      }
-    ];
-
-    security.sudo.configFile =
+    assertions = [ {
+      assertion = cfg.package.pname != "sudo-rs";
+      message = ''
+        NixOS' `sudo` module does not support `sudo-rs`; see `security.sudo-rs` instead.
+      '';
+    } ];
+
+    security.sudo.extraRules =
+      let
+        defaultRule = { users ? [], groups ? [], opts ? [] }: [ {
+          inherit users groups;
+          commands = [ {
+            command = "ALL";
+            options = opts ++ cfg.defaultOptions;
+          } ];
+        } ];
+      in mkMerge [
+        # This is ordered before users' `mkBefore` rules,
+        # so as not to introduce unexpected changes.
+        (mkOrder 400 (defaultRule { users = [ "root" ]; }))
+
+        # This is ordered to show before (most) other rules, but
+        # late-enough for a user to `mkBefore` it.
+        (mkOrder 600 (defaultRule {
+          groups = [ "wheel" ];
+          opts = (optional (!cfg.wheelNeedsPassword) "NOPASSWD");
+        }))
+      ];
+
+    security.sudo.configFile = concatStringsSep "\n" (filter (s: s != "") [
       ''
         # Don't edit this file. Set the NixOS options ‘security.sudo.configFile’
         # or ‘security.sudo.extraRules’ instead.
-
-        # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
-        Defaults env_keep+=SSH_AUTH_SOCK
-
-        # "root" is allowed to do anything.
-        root        ALL=(ALL:ALL) SETENV: ALL
-
-        # extraRules
-        ${concatStringsSep "\n" (
-          lists.flatten (
-            map (
-              rule: optionals (length rule.commands != 0) [
-                (map (user: "${toUserString user}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.users)
-                (map (group: "${toGroupString group}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.groups)
-              ]
-            ) cfg.extraRules
-          )
-        )}
-
+      ''
+      (pipe cfg.extraRules [
+        (filter (rule: length rule.commands != 0))
+        (map (rule: [
+          (map (user: "${toUserString user}     ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.users)
+          (map (group: "${toGroupString group}  ${rule.host}=(${rule.runAs})    ${toCommandsString rule.commands}") rule.groups)
+        ]))
+        flatten
+        (concatStringsSep "\n")
+      ])
+      "\n"
+      (optionalString (cfg.extraConfig != "") ''
+        # extraConfig
         ${cfg.extraConfig}
-      '';
+      '')
+    ]);
 
     security.wrappers = let
       owner = "root";
@@ -243,7 +256,7 @@ in
       };
     };
 
-    environment.systemPackages = [ sudo ];
+    environment.systemPackages = [ cfg.package ];
 
     security.pam.services.sudo = { sshAgentAuth = true; usshAuth = true; };
 
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 12255d8392fe9..a298686b34e97 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -5,8 +5,29 @@ let
 
   parentWrapperDir = dirOf wrapperDir;
 
-  securityWrapper = pkgs.callPackage ./wrapper.nix {
-    inherit parentWrapperDir;
+  # This is security-sensitive code, and glibc vulns happen from time to time.
+  # musl is security-focused and generally more minimal, so it's a better choice here.
+  # The dynamic linker is still a fairly complex piece of code, and the wrappers are
+  # quite small, so linking it statically is more appropriate.
+  securityWrapper = sourceProg : pkgs.pkgsStatic.callPackage ./wrapper.nix {
+    inherit sourceProg;
+
+    # glibc definitions of insecure environment variables
+    #
+    # We extract the single header file we need into its own derivation,
+    # so that we don't have to pull full glibc sources to build wrappers.
+    #
+    # They're taken from pkgs.glibc so that we don't have to keep as close
+    # an eye on glibc changes. Not every relevant variable is in this header,
+    # so we maintain a slightly stricter list in wrapper.c itself as well.
+    unsecvars = lib.overrideDerivation (pkgs.srcOnly pkgs.glibc)
+      ({ name, ... }: {
+        name = "${name}-unsecvars";
+        installPhase = ''
+          mkdir $out
+          cp sysdeps/generic/unsecvars.h $out
+        '';
+      });
   };
 
   fileModeType =
@@ -91,8 +112,7 @@ let
     , ...
     }:
     ''
-      cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
-      echo -n "${source}" > "$wrapperDir/${program}.real"
+      cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
 
       # Prevent races
       chmod 0000 "$wrapperDir/${program}"
@@ -119,8 +139,7 @@ let
     , ...
     }:
     ''
-      cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
-      echo -n "${source}" > "$wrapperDir/${program}.real"
+      cp ${securityWrapper source}/bin/security-wrapper "$wrapperDir/${program}"
 
       # Prevent races
       chmod 0000 "$wrapperDir/${program}"
@@ -248,39 +267,48 @@ in
       export PATH="${wrapperDir}:$PATH"
     '';
 
-    security.apparmor.includes."nixos/security.wrappers" = ''
-      include "${pkgs.apparmorRulesFromClosure { name="security.wrappers"; } [
-        securityWrapper
+    security.apparmor.includes = lib.mapAttrs' (wrapName: wrap: lib.nameValuePair
+     "nixos/security.wrappers/${wrapName}" ''
+      include "${pkgs.apparmorRulesFromClosure { name="security.wrappers.${wrapName}"; } [
+        (securityWrapper wrap.source)
       ]}"
-    '';
-
-    ###### wrappers activation script
-    system.activationScripts.wrappers =
-      lib.stringAfter [ "specialfs" "users" ]
-        ''
-          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" mkWrappedPrograms}
-
-          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}"
+      mrpx ${wrap.source},
+    '') wrappers;
+
+    systemd.services.suid-sgid-wrappers = {
+      description = "Create SUID/SGID Wrappers";
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      after = [ "systemd-sysusers.service" ];
+      unitConfig.DefaultDependencies = false;
+      unitConfig.RequiresMountsFor = [ "/nix/store" "/run/wrappers" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        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" mkWrappedPrograms}
+
+        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
+      '';
+    };
 
     ###### wrappers consistency checks
     system.checks = lib.singleton (pkgs.runCommandLocal
diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c
index 17776a97af81e..3277e7ef6f799 100644
--- a/nixos/modules/security/wrappers/wrapper.c
+++ b/nixos/modules/security/wrappers/wrapper.c
@@ -17,17 +17,18 @@
 #include <syscall.h>
 #include <byteswap.h>
 
+// imported from glibc
+#include "unsecvars.h"
+
+#ifndef SOURCE_PROG
+#error SOURCE_PROG should be defined via preprocessor commandline
+#endif
+
 // aborts when false, printing the failed expression
 #define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
-// aborts when returns non-zero, printing the failed expression and errno
-#define MUSTSUCCEED(expr) ((expr) ? print_errno_and_die(#expr) : (void) 0)
 
 extern char **environ;
 
-// The WRAPPER_DIR macro is supplied at compile time so that it cannot
-// be changed at runtime
-static char *wrapper_dir = WRAPPER_DIR;
-
 // Wrapper debug variable name
 static char *wrapper_debug = "WRAPPER_DEBUG";
 
@@ -45,12 +46,6 @@ static noreturn void assert_failure(const char *assertion) {
     abort();
 }
 
-static noreturn void print_errno_and_die(const char *assertion) {
-    fprintf(stderr, "Call `%s` in NixOS's wrapper.c failed: %s\n", assertion, strerror(errno));
-    fflush(stderr);
-    abort();
-}
-
 int get_last_cap(unsigned *last_cap) {
     FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r");
     if (file == NULL) {
@@ -151,115 +146,66 @@ static int make_caps_ambient(const char *self_path) {
     return 0;
 }
 
-int readlink_malloc(const char *p, char **ret) {
-    size_t l = FILENAME_MAX+1;
-    int r;
-
-    for (;;) {
-        char *c = calloc(l, sizeof(char));
-        if (!c) {
-            return -ENOMEM;
-        }
-
-        ssize_t n = readlink(p, c, l-1);
-        if (n < 0) {
-            r = -errno;
-            free(c);
-            return r;
-        }
-
-        if ((size_t) n < l-1) {
-            c[n] = 0;
-            *ret = c;
-            return 0;
-        }
-
-        free(c);
-        l *= 2;
-    }
-}
+// These are environment variable aliases for glibc tunables.
+// This list shouldn't grow further, since this is a legacy mechanism.
+// Any future tunables are expected to only be accessible through GLIBC_TUNABLES.
+//
+// They are not included in the glibc-provided UNSECURE_ENVVARS list,
+// since any SUID executable ignores them. This wrapper also serves
+// executables that are merely granted ambient capabilities, rather than
+// being SUID, and hence don't run in secure mode. We'd like them to
+// defend those in depth as well, so we clear these explicitly.
+//
+// Except for MALLOC_CHECK_ (which is marked SXID_ERASE), these are all
+// marked SXID_IGNORE (ignored in secure mode), so even the glibc version
+// of this wrapper would leave them intact.
+#define UNSECURE_ENVVARS_TUNABLES \
+    "MALLOC_CHECK_\0" \
+    "MALLOC_TOP_PAD_\0" \
+    "MALLOC_PERTURB_\0" \
+    "MALLOC_MMAP_THRESHOLD_\0" \
+    "MALLOC_TRIM_THRESHOLD_\0" \
+    "MALLOC_MMAP_MAX_\0" \
+    "MALLOC_ARENA_MAX\0" \
+    "MALLOC_ARENA_TEST\0"
 
 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) {
-        fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size));
-    }
-
-    unsigned int ruid, euid, suid, rgid, egid, sgid;
-    MUSTSUCCEED(getresuid(&ruid, &euid, &suid));
-    MUSTSUCCEED(getresgid(&rgid, &egid, &sgid));
-
-    // If true, then we did not benefit from setuid privilege escalation,
-    // where the original uid is still in ruid and different from euid == suid.
-    int didnt_suid = (ruid == euid) && (euid == suid);
-    // If true, then we did not benefit from setgid privilege escalation
-    int didnt_sgid = (rgid == egid) && (egid == sgid);
-
-
-    // Make sure that we are being executed from the right location,
-    // i.e., `safe_wrapper_dir'.  This is to prevent someone from creating
-    // hard link `X' from some other location, along with a false
-    // `X.real' file, to allow arbitrary programs from being executed
-    // with elevated capabilities.
-    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]);
-
-    // If we got privileges with the fs set[ug]id bit, check that the privilege we
-    // got matches the one one we expected, ie that our effective uid/gid
-    // matches the uid/gid of `self_path`. This ensures that we were executed as
-    // `self_path', and not, say, as some other setuid program.
-    // We don't check that if we did not benefit from the set[ug]id bit, as
-    // can be the case in nosuid mounts or user namespaces.
-    struct stat st;
-    ASSERT(lstat(self_path, &st) != -1);
 
-    // if the wrapper gained privilege with suid, check that we got the uid of the file owner
-    ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid));
-    // if the wrapper gained privilege with sgid, check that we got the gid of the file group
-    ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid));
-    // same, but with suid instead of euid
-    ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid));
-    ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == sgid));
-
-    // And, of course, we shouldn't be writable.
-    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));
-
-    int fd_self = open(real_fn, O_RDONLY);
-    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);
-    source_prog[len] = 0;
-
-    close(fd_self);
+    int debug = getenv(wrapper_debug) != NULL;
+
+    // Drop insecure environment variables explicitly
+    //
+    // glibc does this automatically in SUID binaries, but we'd like to cover this:
+    //
+    //  a) before it gets to glibc
+    //  b) in binaries that are only granted ambient capabilities by the wrapper,
+    //     but don't run with an altered effective UID/GID, nor directly gain
+    //     capabilities themselves, and thus don't run in secure mode.
+    //
+    // We're using musl, which doesn't drop environment variables in secure mode,
+    // and we'd also like glibc-specific variables to be covered.
+    //
+    // If we don't explicitly unset them, it's quite easy to just set LD_PRELOAD,
+    // have it passed through to the wrapped program, and gain privileges.
+    for (char *unsec = UNSECURE_ENVVARS_TUNABLES UNSECURE_ENVVARS; *unsec; unsec = strchr(unsec, 0) + 1) {
+        if (debug) {
+            fprintf(stderr, "unsetting %s\n", unsec);
+        }
+        unsetenv(unsec);
+    }
 
     // Read the capabilities set on the wrapper and raise them in to
     // the ambient set so the program we're wrapping receives the
     // capabilities too!
-    if (make_caps_ambient(self_path) != 0) {
-        free(self_path);
+    if (make_caps_ambient("/proc/self/exe") != 0) {
         return 1;
     }
-    free(self_path);
 
-    execve(source_prog, argv, environ);
+    execve(SOURCE_PROG, argv, environ);
     
     fprintf(stderr, "%s: cannot run `%s': %s\n",
-        argv[0], source_prog, strerror(errno));
+        argv[0], SOURCE_PROG, strerror(errno));
 
     return 1;
 }
diff --git a/nixos/modules/security/wrappers/wrapper.nix b/nixos/modules/security/wrappers/wrapper.nix
index e3620fb222d2c..ca4b27bff1801 100644
--- a/nixos/modules/security/wrappers/wrapper.nix
+++ b/nixos/modules/security/wrappers/wrapper.nix
@@ -1,13 +1,12 @@
-{ stdenv, linuxHeaders, parentWrapperDir, debug ? false }:
+{ stdenv, unsecvars, linuxHeaders, sourceProg, debug ? false }:
 # For testing:
-# $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }'
+# $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { sourceProg = "${pkgs.hello}/bin/hello"; debug = true; }'
 stdenv.mkDerivation {
-  name = "security-wrapper";
+  name = "security-wrapper-${baseNameOf sourceProg}";
   buildInputs = [ linuxHeaders ];
   dontUnpack = true;
-  hardeningEnable = [ "pie" ];
   CFLAGS = [
-    ''-DWRAPPER_DIR="${parentWrapperDir}"''
+    ''-DSOURCE_PROG="${sourceProg}"''
   ] ++ (if debug then [
     "-Werror" "-Og" "-g"
   ] else [
@@ -16,6 +15,6 @@ stdenv.mkDerivation {
   dontStrip = debug;
   installPhase = ''
     mkdir -p $out/bin
-    $CC $CFLAGS ${./wrapper.c} -o $out/bin/security-wrapper
+    $CC $CFLAGS ${./wrapper.c} -I${unsecvars} -o $out/bin/security-wrapper
   '';
 }
diff --git a/nixos/modules/services/admin/meshcentral.nix b/nixos/modules/services/admin/meshcentral.nix
index 22f31e9526226..d056356568dab 100644
--- a/nixos/modules/services/admin/meshcentral.nix
+++ b/nixos/modules/services/admin/meshcentral.nix
@@ -6,12 +6,7 @@ let
 in with lib; {
   options.services.meshcentral = with types; {
     enable = mkEnableOption (lib.mdDoc "MeshCentral computer management server");
-    package = mkOption {
-      description = lib.mdDoc "MeshCentral package to use. Replacing this may be necessary to add dependencies for extra functionality.";
-      type = types.package;
-      default = pkgs.meshcentral;
-      defaultText = literalExpression "pkgs.meshcentral";
-    };
+    package = mkPackageOption pkgs "meshcentral" { };
     settings = mkOption {
       description = lib.mdDoc ''
         Settings for MeshCentral. Refer to upstream documentation for details:
diff --git a/nixos/modules/services/admin/pgadmin.nix b/nixos/modules/services/admin/pgadmin.nix
index 390c80d1a2d42..20b6b6670d9cc 100644
--- a/nixos/modules/services/admin/pgadmin.nix
+++ b/nixos/modules/services/admin/pgadmin.nix
@@ -3,7 +3,6 @@
 with lib;
 
 let
-  pkg = pkgs.pgadmin4;
   cfg = config.services.pgadmin;
 
   _base = with types; [ int bool str ];
@@ -36,6 +35,8 @@ in
       default = 5050;
     };
 
+    package = mkPackageOptionMD pkgs "pgadmin4" { };
+
     initialEmail = mkOption {
       description = lib.mdDoc "Initial email for the pgAdmin account";
       type = types.str;
@@ -43,12 +44,19 @@ in
 
     initialPasswordFile = mkOption {
       description = lib.mdDoc ''
-        Initial password file for the pgAdmin account.
+        Initial password file for the pgAdmin account. Minimum length by default is 6.
+        Please see `services.pgadmin.minimumPasswordLength`.
         NOTE: Should be string not a store path, to prevent the password from being world readable
       '';
       type = types.path;
     };
 
+    minimumPasswordLength = mkOption {
+      description = lib.mdDoc "Minimum length of the password";
+      type = types.int;
+      default = 6;
+    };
+
     emailServer = {
       enable = mkOption {
         description = lib.mdDoc ''
@@ -115,7 +123,9 @@ in
 
     services.pgadmin.settings = {
       DEFAULT_SERVER_PORT = cfg.port;
+      PASSWORD_LENGTH_MIN = cfg.minimumPasswordLength;
       SERVER_MODE = true;
+      UPGRADE_CHECK_ENABLED = false;
     } // (optionalAttrs cfg.openFirewall {
       DEFAULT_SERVER = mkDefault "::";
     }) // (optionalAttrs cfg.emailServer.enable {
@@ -139,6 +149,14 @@ in
 
       preStart = ''
         # NOTE: this is idempotent (aka running it twice has no effect)
+        # Check here for password length to prevent pgadmin from starting
+        # and presenting a hard to find error message
+        # see https://github.com/NixOS/nixpkgs/issues/270624
+        PW_LENGTH=$(wc -m < ${escapeShellArg cfg.initialPasswordFile})
+        if [ $PW_LENGTH -lt ${toString cfg.minimumPasswordLength} ]; then
+            echo "Password must be at least ${toString cfg.minimumPasswordLength} characters long"
+            exit 1
+        fi
         (
           # Email address:
           echo ${escapeShellArg cfg.initialEmail}
@@ -150,7 +168,7 @@ in
           echo "$PW"
           # Retype password:
           echo "$PW"
-        ) | ${pkg}/bin/pgadmin4-setup
+        ) | ${cfg.package}/bin/pgadmin4-cli setup-db
       '';
 
       restartTriggers = [
@@ -162,7 +180,7 @@ in
         DynamicUser = true;
         LogsDirectory = "pgadmin";
         StateDirectory = "pgadmin";
-        ExecStart = "${pkg}/bin/pgadmin4";
+        ExecStart = "${cfg.package}/bin/pgadmin4";
       };
     };
 
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index 11dabf0b51c80..7dce9d2429168 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -26,14 +26,7 @@ in
         '';
       };
 
-      package = mkOption {
-        default = pkgs.rabbitmq-server;
-        type = types.package;
-        defaultText = literalExpression "pkgs.rabbitmq-server";
-        description = lib.mdDoc ''
-          Which rabbitmq package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "rabbitmq-server" { };
 
       listenAddress = mkOption {
         default = "127.0.0.1";
diff --git a/nixos/modules/services/audio/botamusique.nix b/nixos/modules/services/audio/botamusique.nix
index 5d3f7db12bc9b..42227cb147224 100644
--- a/nixos/modules/services/audio/botamusique.nix
+++ b/nixos/modules/services/audio/botamusique.nix
@@ -14,12 +14,7 @@ in
   options.services.botamusique = {
     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 = lib.mdDoc "The botamusique package to use.";
-    };
+    package = mkPackageOption pkgs "botamusique" { };
 
     settings = mkOption {
       type = with types; submodule {
diff --git a/nixos/modules/services/audio/castopod.md b/nixos/modules/services/audio/castopod.md
new file mode 100644
index 0000000000000..ee8590737a7c7
--- /dev/null
+++ b/nixos/modules/services/audio/castopod.md
@@ -0,0 +1,22 @@
+# Castopod {#module-services-castopod}
+
+Castopod is an open-source hosting platform made for podcasters who want to engage and interact with their audience.
+
+## Quickstart {#module-services-castopod-quickstart}
+
+Use the following configuration to start a public instance of Castopod on `castopod.example.com` domain:
+
+```nix
+networking.firewall.allowedTCPPorts = [ 80 443 ];
+services.castopod = {
+  enable = true;
+  database.createLocally = true;
+  nginx.virtualHost = {
+    serverName = "castopod.example.com";
+    enableACME = true;
+    forceSSL = true;
+  };
+};
+```
+
+Go to `https://castopod.example.com/cp-install` to create superadmin account after applying the above configuration.
diff --git a/nixos/modules/services/audio/castopod.nix b/nixos/modules/services/audio/castopod.nix
new file mode 100644
index 0000000000000..b782b54891479
--- /dev/null
+++ b/nixos/modules/services/audio/castopod.nix
@@ -0,0 +1,287 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.castopod;
+  fpm = config.services.phpfpm.pools.castopod;
+
+  user = "castopod";
+  stateDirectory = "/var/lib/castopod";
+
+  # https://docs.castopod.org/getting-started/install.html#requirements
+  phpPackage = pkgs.php.withExtensions ({ enabled, all }: with all; [
+    intl
+    curl
+    mbstring
+    gd
+    exif
+    mysqlnd
+  ] ++ enabled);
+in
+{
+  meta.doc = ./castopod.md;
+  meta.maintainers = with lib.maintainers; [ alexoundos misuzu ];
+
+  options.services = {
+    castopod = {
+      enable = lib.mkEnableOption (lib.mdDoc "Castopod");
+      package = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.castopod;
+        defaultText = lib.literalMD "pkgs.castopod";
+        description = lib.mdDoc "Which Castopod package to use.";
+      };
+      database = {
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Create the database and database user locally.
+          '';
+        };
+        hostname = lib.mkOption {
+          type = lib.types.str;
+          default = "localhost";
+          description = lib.mdDoc "Database hostname.";
+        };
+        name = lib.mkOption {
+          type = lib.types.str;
+          default = "castopod";
+          description = lib.mdDoc "Database name.";
+        };
+        user = lib.mkOption {
+          type = lib.types.str;
+          default = user;
+          description = lib.mdDoc "Database user.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/run/keys/castopod-dbpassword";
+          description = lib.mdDoc ''
+            A file containing the password corresponding to
+            [](#opt-services.castopod.database.user).
+          '';
+        };
+      };
+      settings = lib.mkOption {
+        type = with lib.types; attrsOf (oneOf [ str int bool ]);
+        default = { };
+        example = {
+          "email.protocol" = "smtp";
+          "email.SMTPHost" = "localhost";
+          "email.SMTPUser" = "myuser";
+          "email.fromEmail" = "castopod@example.com";
+        };
+        description = lib.mdDoc ''
+          Environment variables used for Castopod.
+          See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
+          for available environment variables.
+        '';
+      };
+      environmentFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/castopod-env";
+        description = lib.mdDoc ''
+          Environment file to inject e.g. secrets into the configuration.
+          See [](https://code.castopod.org/adaures/castopod/-/blob/main/.env.example)
+          for available environment variables.
+        '';
+      };
+      configureNginx = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc "Configure nginx as a reverse proxy for CastoPod.";
+      };
+      localDomain = lib.mkOption {
+        type = lib.types.str;
+        example = "castopod.example.org";
+        description = lib.mdDoc "The domain serving your CastoPod instance.";
+      };
+      poolSettings = lib.mkOption {
+        type = with lib.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 Castopod's PHP pool. See the documentation on `php-fpm.conf` for details on configuration directives.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.castopod.settings =
+      let
+        sslEnabled = with config.services.nginx.virtualHosts.${cfg.localDomain}; addSSL || forceSSL || onlySSL || enableACME || useACMEHost != null;
+        baseURL = "http${lib.optionalString sslEnabled "s"}://${cfg.localDomain}";
+      in
+      lib.mapAttrs (name: lib.mkDefault) {
+        "app.forceGlobalSecureRequests" = sslEnabled;
+        "app.baseURL" = baseURL;
+
+        "media.baseURL" = "/";
+        "media.root" = "media";
+        "media.storage" = stateDirectory;
+
+        "admin.gateway" = "admin";
+        "auth.gateway" = "auth";
+
+        "database.default.hostname" = cfg.database.hostname;
+        "database.default.database" = cfg.database.name;
+        "database.default.username" = cfg.database.user;
+        "database.default.DBPrefix" = "cp_";
+
+        "cache.handler" = "file";
+      };
+
+    services.phpfpm.pools.castopod = {
+      inherit user;
+      group = config.services.nginx.group;
+      phpPackage = phpPackage;
+      phpOptions = ''
+        # https://code.castopod.org/adaures/castopod/-/blob/main/docker/production/app/uploads.ini
+        file_uploads = On
+        memory_limit = 512M
+        upload_max_filesize = 500M
+        post_max_size = 512M
+        max_execution_time = 300
+        max_input_time = 300
+      '';
+      settings = {
+        "listen.owner" = config.services.nginx.user;
+        "listen.group" = config.services.nginx.group;
+      } // cfg.poolSettings;
+    };
+
+    systemd.services.castopod-setup = {
+      after = lib.optional config.services.mysql.enable "mysql.service";
+      requires = lib.optional config.services.mysql.enable "mysql.service";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.openssl phpPackage ];
+      script =
+        let
+          envFile = "${stateDirectory}/.env";
+          media = "${cfg.settings."media.storage"}/${cfg.settings."media.root"}";
+        in
+        ''
+          mkdir -p ${stateDirectory}/writable/{cache,logs,session,temp,uploads}
+
+          if [ ! -d ${lib.escapeShellArg media} ]; then
+            cp --no-preserve=mode,ownership -r ${cfg.package}/share/castopod/public/media ${lib.escapeShellArg media}
+          fi
+
+          if [ ! -f ${stateDirectory}/salt ]; then
+            openssl rand -base64 33 > ${stateDirectory}/salt
+          fi
+
+          cat <<'EOF' > ${envFile}
+          ${lib.generators.toKeyValue { } cfg.settings}
+          EOF
+
+          echo "analytics.salt=$(cat ${stateDirectory}/salt)" >> ${envFile}
+
+          ${if (cfg.database.passwordFile != null) then ''
+            echo "database.default.password=$(cat ${lib.escapeShellArg cfg.database.passwordFile})" >> ${envFile}
+          '' else ''
+            echo "database.default.password=" >> ${envFile}
+          ''}
+
+          ${lib.optionalString (cfg.environmentFile != null) ''
+            cat ${lib.escapeShellArg cfg.environmentFile}) >> ${envFile}
+          ''}
+
+          php spark castopod:database-update
+        '';
+      serviceConfig = {
+        StateDirectory = "castopod";
+        WorkingDirectory = "${cfg.package}/share/castopod";
+        Type = "oneshot";
+        RemainAfterExit = true;
+        User = user;
+        Group = config.services.nginx.group;
+      };
+    };
+
+    systemd.services.castopod-scheduled = {
+      after = [ "castopod-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ phpPackage ];
+      script = ''
+        php public/index.php scheduled-activities
+        php public/index.php scheduled-websub-publish
+        php public/index.php scheduled-video-clips
+      '';
+      serviceConfig = {
+        StateDirectory = "castopod";
+        WorkingDirectory = "${cfg.package}/share/castopod";
+        Type = "oneshot";
+        User = user;
+        Group = config.services.nginx.group;
+      };
+    };
+
+    systemd.timers.castopod-scheduled = {
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "*-*-* *:*:00";
+        Unit = "castopod-scheduled.service";
+      };
+    };
+
+    services.mysql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      package = lib.mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [{
+        name = cfg.database.user;
+        ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+      }];
+    };
+
+    services.nginx = lib.mkIf cfg.configureNginx {
+      enable = true;
+      virtualHosts."${cfg.localDomain}" = {
+        root = lib.mkForce "${cfg.package}/share/castopod/public";
+
+        extraConfig = ''
+          try_files $uri $uri/ /index.php?$args;
+          index index.php index.html;
+        '';
+
+        locations."^~ /${cfg.settings."media.root"}/" = {
+          root = cfg.settings."media.storage";
+          extraConfig = ''
+            add_header Access-Control-Allow-Origin "*";
+            expires max;
+            access_log off;
+          '';
+        };
+
+        locations."~ \.php$" = {
+          fastcgiParams = {
+            SERVER_NAME = "$host";
+          };
+          extraConfig = ''
+            fastcgi_intercept_errors on;
+            fastcgi_index index.php;
+            fastcgi_pass unix:${fpm.socket};
+            try_files $uri =404;
+            fastcgi_read_timeout 3600;
+            fastcgi_send_timeout 3600;
+          '';
+        };
+      };
+    };
+
+    users.users.${user} = lib.mapAttrs (name: lib.mkDefault) {
+      description = "Castopod user";
+      isSystemUser = true;
+      group = config.services.nginx.group;
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/gmediarender.nix b/nixos/modules/services/audio/gmediarender.nix
index 2f23232d19cf2..a4cb89098db7a 100644
--- a/nixos/modules/services/audio/gmediarender.nix
+++ b/nixos/modules/services/audio/gmediarender.nix
@@ -41,7 +41,7 @@ in
       '';
     };
 
-    package = mkPackageOptionMD pkgs "gmediarender" {
+    package = mkPackageOption pkgs "gmediarender" {
       default = "gmrender-resurrect";
     };
 
@@ -64,6 +64,7 @@ in
   config = mkIf cfg.enable {
     systemd = {
       services.gmediarender = {
+        wants = [ "network-online.target" ];
         after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         description = "gmediarender server daemon";
diff --git a/nixos/modules/services/audio/gonic.nix b/nixos/modules/services/audio/gonic.nix
index 65cf10f2c4b46..66daeb60b5034 100644
--- a/nixos/modules/services/audio/gonic.nix
+++ b/nixos/modules/services/audio/gonic.nix
@@ -57,6 +57,7 @@ in
         ReadWritePaths = "";
         BindReadOnlyPaths = [
           # gonic can access scrobbling services
+          "-/etc/resolv.conf"
           "-/etc/ssl/certs/ca-certificates.crt"
           builtins.storeDir
           cfg.settings.podcast-path
diff --git a/nixos/modules/services/audio/goxlr-utility.nix b/nixos/modules/services/audio/goxlr-utility.nix
index b719de875c7ff..c047dbb221b16 100644
--- a/nixos/modules/services/audio/goxlr-utility.nix
+++ b/nixos/modules/services/audio/goxlr-utility.nix
@@ -16,7 +16,7 @@ with lib;
           Whether to enable goxlr-utility for controlling your TC-Helicon GoXLR or GoXLR Mini
         '';
       };
-      package = mkPackageOptionMD pkgs "goxlr-utility" { };
+      package = mkPackageOption pkgs "goxlr-utility" { };
       autoStart.xdg = mkOption {
         default = true;
         type = with types; bool;
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
index 105e99cb2f5ed..3869bd974ccef 100644
--- a/nixos/modules/services/audio/jack.nix
+++ b/nixos/modules/services/audio/jack.nix
@@ -20,16 +20,11 @@ in {
           JACK Audio Connection Kit. You need to add yourself to the "jackaudio" group
         '');
 
-        package = mkOption {
+        package = mkPackageOption pkgs "jack2" {
+          example = "jack1";
+        } // {
           # until jack1 promiscuous mode is fixed
           internal = true;
-          type = types.package;
-          default = pkgs.jack2;
-          defaultText = literalExpression "pkgs.jack2";
-          example = literalExpression "pkgs.jack1";
-          description = lib.mdDoc ''
-            The JACK package to use.
-          '';
         };
 
         extraOptions = mkOption {
@@ -225,7 +220,7 @@ in {
         description = "JACK Audio system service user";
         isSystemUser = true;
       };
-      # http://jackaudio.org/faq/linux_rt_config.html
+      # https://jackaudio.org/faq/linux_rt_config.html
       security.pam.loginLimits = [
         { domain = "@jackaudio"; type = "-"; item = "rtprio"; value = "99"; }
         { domain = "@jackaudio"; type = "-"; item = "memlock"; value = "unlimited"; }
diff --git a/nixos/modules/services/audio/jmusicbot.nix b/nixos/modules/services/audio/jmusicbot.nix
index 348c7b25682ea..e7803677d0fd9 100644
--- a/nixos/modules/services/audio/jmusicbot.nix
+++ b/nixos/modules/services/audio/jmusicbot.nix
@@ -9,12 +9,7 @@ in
     services.jmusicbot = {
       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 = lib.mdDoc "JMusicBot package to use";
-      };
+      package = mkPackageOption pkgs "jmusicbot" { };
 
       stateDir = mkOption {
         type = types.path;
@@ -31,6 +26,7 @@ in
   config = mkIf cfg.enable {
     systemd.services.jmusicbot = {
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       description = "Discord music bot that's easy to set up and run yourself!";
       serviceConfig = mkMerge [{
diff --git a/nixos/modules/services/audio/mopidy.nix b/nixos/modules/services/audio/mopidy.nix
index 40e8679f53d74..9d8e67b0ea478 100644
--- a/nixos/modules/services/audio/mopidy.nix
+++ b/nixos/modules/services/audio/mopidy.nix
@@ -76,7 +76,7 @@ in {
 
     systemd.services.mopidy = {
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "sound.target" ];
+      after = [ "network-online.target" "sound.target" ];
       description = "mopidy music player daemon";
       serviceConfig = {
         ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)}";
diff --git a/nixos/modules/services/audio/mympd.nix b/nixos/modules/services/audio/mympd.nix
new file mode 100644
index 0000000000000..f1c7197085d7d
--- /dev/null
+++ b/nixos/modules/services/audio/mympd.nix
@@ -0,0 +1,129 @@
+{ pkgs, config, lib, ... }:
+
+let
+  cfg = config.services.mympd;
+in {
+  options = {
+
+    services.mympd = {
+
+      enable = lib.mkEnableOption (lib.mdDoc "MyMPD server");
+
+      package = lib.mkPackageOption pkgs "mympd" {};
+
+      openFirewall = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Open ports needed for the functionality of the program.
+        '';
+      };
+
+      extraGroups = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
+        example = [ "music" ];
+        description = lib.mdDoc ''
+          Additional groups for the systemd service.
+        '';
+      };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule {
+          freeformType = with lib.types; attrsOf (nullOr (oneOf [ str bool int ]));
+          options = {
+            http_port = lib.mkOption {
+              type = lib.types.port;
+              description = lib.mdDoc ''
+                The HTTP port where mympd's web interface will be available.
+
+                The HTTPS/SSL port can be configured via {option}`config`.
+              '';
+              example = "8080";
+            };
+
+            ssl = lib.mkOption {
+              type = lib.types.bool;
+              description = lib.mdDoc ''
+                Whether to enable listening on the SSL port.
+
+                Refer to <https://jcorporation.github.io/myMPD/configuration/configuration-files#ssl-options>
+                for more information.
+              '';
+              default = false;
+            };
+          };
+        };
+        description = lib.mdDoc ''
+          Manages the configuration files declaratively. For all the configuration
+          options, see <https://jcorporation.github.io/myMPD/configuration/configuration-files>.
+
+          Each key represents the "File" column from the upstream configuration table, and the
+          value is the content of that file.
+        '';
+      };
+    };
+
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.mympd = {
+      # upstream service config: https://github.com/jcorporation/myMPD/blob/master/contrib/initscripts/mympd.service.in
+      after = [ "mpd.service" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = with lib; ''
+        config_dir="/var/lib/mympd/config"
+        mkdir -p "$config_dir"
+
+        ${pipe cfg.settings [
+          (mapAttrsToList (name: value: ''
+            echo -n "${if isBool value then boolToString value else toString value}" > "$config_dir/${name}"
+            ''))
+          (concatStringsSep "\n")
+        ]}
+      '';
+      unitConfig = {
+        Description = "myMPD server daemon";
+        Documentation = "man:mympd(1)";
+      };
+      serviceConfig = {
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+        DynamicUser = true;
+        ExecStart = lib.getExe cfg.package;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictRealtime = true;
+        StateDirectory = "mympd";
+        CacheDirectory = "mympd";
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK AF_UNIX";
+        RestrictNamespaces = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "@system-service";
+        SupplementaryGroups = cfg.extraGroups;
+      };
+    };
+
+    networking.firewall = lib.mkMerge [
+      (lib.mkIf cfg.openFirewall {
+        allowedTCPPorts = [ cfg.settings.http_port ];
+      })
+      (lib.mkIf (cfg.openFirewall && cfg.settings.ssl && cfg.settings.ssl_port != null) {
+        allowedTCPPorts = [ cfg.settings.ssl_port ];
+      })
+    ];
+
+  };
+
+  meta.maintainers = [ lib.maintainers.eliandoran ];
+
+}
diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix
index e18e61eb6d44b..e44fc822e4add 100644
--- a/nixos/modules/services/audio/navidrome.nix
+++ b/nixos/modules/services/audio/navidrome.nix
@@ -11,7 +11,7 @@ in {
 
       enable = mkEnableOption (lib.mdDoc "Navidrome music server");
 
-      package = mkPackageOptionMD pkgs "navidrome" { };
+      package = mkPackageOption pkgs "navidrome" { };
 
       settings = mkOption rec {
         type = settingsFormat.type;
@@ -28,10 +28,17 @@ in {
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Whether to open the TCP port in the firewall";
+      };
     };
   };
 
   config = mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.settings.Port];
+
     systemd.services.navidrome = {
       description = "Navidrome Media Server";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/audio/slimserver.nix b/nixos/modules/services/audio/slimserver.nix
index 9fbc68b71364e..73cda08c57422 100644
--- a/nixos/modules/services/audio/slimserver.nix
+++ b/nixos/modules/services/audio/slimserver.nix
@@ -19,12 +19,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.slimserver;
-        defaultText = literalExpression "pkgs.slimserver";
-        description = lib.mdDoc "Slimserver package to use.";
-      };
+      package = mkPackageOption pkgs "slimserver" { };
 
       dataDir = mkOption {
         type = types.path;
@@ -54,7 +49,7 @@ in {
       serviceConfig = {
         User = "slimserver";
         # Issue 40589: Disable broken image/video support (audio still works!)
-        ExecStart = "${cfg.package}/slimserver.pl --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
+        ExecStart = "${lib.getExe cfg.package} --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
       };
     };
 
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix
index 975be5a87cba9..1194b6f200d70 100644
--- a/nixos/modules/services/audio/spotifyd.nix
+++ b/nixos/modules/services/audio/spotifyd.nix
@@ -50,6 +50,7 @@ in
 
     systemd.services.spotifyd = {
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" "sound.target" ];
       description = "spotifyd, a Spotify playing daemon";
       environment.SHELL = "/bin/sh";
diff --git a/nixos/modules/services/audio/tts.nix b/nixos/modules/services/audio/tts.nix
index 1a355c8ee39f5..0d93224ec0306 100644
--- a/nixos/modules/services/audio/tts.nix
+++ b/nixos/modules/services/audio/tts.nix
@@ -134,6 +134,7 @@ in
           ProtectProc = "invisible";
           ProcSubset = "pid";
           RestrictAddressFamilies = [
+            "AF_UNIX"
             "AF_INET"
             "AF_INET6"
           ];
diff --git a/nixos/modules/services/audio/wyoming/faster-whisper.nix b/nixos/modules/services/audio/wyoming/faster-whisper.nix
index 1fb67ecfe5060..dd7f62744cd02 100644
--- a/nixos/modules/services/audio/wyoming/faster-whisper.nix
+++ b/nixos/modules/services/audio/wyoming/faster-whisper.nix
@@ -12,7 +12,7 @@ let
     mkOption
     mdDoc
     mkEnableOption
-    mkPackageOptionMD
+    mkPackageOption
     types
     ;
 
@@ -24,7 +24,7 @@ in
 
 {
   options.services.wyoming.faster-whisper = with types; {
-    package = mkPackageOptionMD pkgs "wyoming-faster-whisper" { };
+    package = mkPackageOption pkgs "wyoming-faster-whisper" { };
 
     servers = mkOption {
       default = {};
@@ -37,6 +37,9 @@ in
             enable = mkEnableOption (mdDoc "Wyoming faster-whisper server");
 
             model = mkOption {
+              # Intersection between available and referenced models here:
+              # https://github.com/rhasspy/models/releases/tag/v1.0
+              # https://github.com/rhasspy/rhasspy3/blob/wyoming-v1/programs/asr/faster-whisper/server/wyoming_faster_whisper/download.py#L17-L27
               type = enum [
                 "tiny"
                 "tiny-int8"
@@ -44,7 +47,6 @@ in
                 "base-int8"
                 "small"
                 "small-int8"
-                "medium"
                 "medium-int8"
               ];
               default = "tiny-int8";
@@ -119,6 +121,7 @@ in
   in mkIf (cfg.servers != {}) {
     systemd.services = mapAttrs' (server: options:
       nameValuePair "wyoming-faster-whisper-${server}" {
+        inherit (options) enable;
         description = "Wyoming faster-whisper server instance ${server}";
         after = [
           "network-online.target"
@@ -136,6 +139,7 @@ in
               --data-dir $STATE_DIRECTORY \
               --download-dir $STATE_DIRECTORY \
               --uri ${options.uri} \
+              --device ${options.device} \
               --model ${options.model} \
               --language ${options.language} \
               --beam-size ${options.beamSize} ${options.extraArgs}
@@ -143,6 +147,8 @@ in
           CapabilityBoundingSet = "";
           DeviceAllow = if builtins.elem options.device [ "cuda" "auto" ] then [
             # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
+            # CUDA not working? Check DeviceAllow and PrivateDevices first!
+            "/dev/nvidia0"
             "/dev/nvidia1"
             "/dev/nvidia2"
             "/dev/nvidia3"
@@ -157,7 +163,6 @@ in
           DevicePolicy = "closed";
           LockPersonality = true;
           MemoryDenyWriteExecute = true;
-          PrivateDevices = true;
           PrivateUsers = true;
           ProtectHome = true;
           ProtectHostname = true;
diff --git a/nixos/modules/services/audio/wyoming/openwakeword.nix b/nixos/modules/services/audio/wyoming/openwakeword.nix
new file mode 100644
index 0000000000000..252f70be2baa4
--- /dev/null
+++ b/nixos/modules/services/audio/wyoming/openwakeword.nix
@@ -0,0 +1,163 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.wyoming.openwakeword;
+
+  inherit (lib)
+    concatStringsSep
+    concatMapStringsSep
+    escapeShellArgs
+    mkOption
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkPackageOption
+    mkRemovedOptionModule
+    types
+    ;
+
+  inherit (builtins)
+    toString
+    ;
+
+in
+
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "wyoming" "openwakeword" "models" ] "Configuring models has been removed, they are now dynamically discovered and loaded at runtime")
+  ];
+
+  meta.buildDocsInSandbox = false;
+
+  options.services.wyoming.openwakeword = with types; {
+    enable = mkEnableOption (mdDoc "Wyoming openWakeWord server");
+
+    package = mkPackageOption pkgs "wyoming-openwakeword" { };
+
+    uri = mkOption {
+      type = strMatching "^(tcp|unix)://.*$";
+      default = "tcp://0.0.0.0:10400";
+      example = "tcp://192.0.2.1:5000";
+      description = mdDoc ''
+        URI to bind the wyoming server to.
+      '';
+    };
+
+    customModelsDirectories = mkOption {
+      type = listOf types.path;
+      default = [];
+      description = lib.mdDoc ''
+        Paths to directories with custom wake word models (*.tflite model files).
+      '';
+    };
+
+    preloadModels = mkOption {
+      type = listOf str;
+      default = [
+        "ok_nabu"
+      ];
+      example = [
+        # wyoming_openwakeword/models/*.tflite
+        "alexa"
+        "hey_jarvis"
+        "hey_mycroft"
+        "hey_rhasspy"
+        "ok_nabu"
+      ];
+      description = mdDoc ''
+        List of wake word models to preload after startup.
+      '';
+    };
+
+    threshold = mkOption {
+      type = float;
+      default = 0.5;
+      description = mdDoc ''
+        Activation threshold (0-1), where higher means fewer activations.
+
+        See trigger level for the relationship between activations and
+        wake word detections.
+      '';
+      apply = toString;
+    };
+
+    triggerLevel = mkOption {
+      type = int;
+      default = 1;
+      description = mdDoc ''
+        Number of activations before a detection is registered.
+
+        A higher trigger level means fewer detections.
+      '';
+      apply = toString;
+    };
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [ ];
+      description = mdDoc ''
+        Extra arguments to pass to the server commandline.
+      '';
+      apply = escapeShellArgs;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services."wyoming-openwakeword" = {
+      description = "Wyoming openWakeWord server";
+      after = [
+        "network-online.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      serviceConfig = {
+        DynamicUser = true;
+        User = "wyoming-openwakeword";
+        # https://github.com/home-assistant/addons/blob/master/openwakeword/rootfs/etc/s6-overlay/s6-rc.d/openwakeword/run
+        ExecStart = concatStringsSep " " [
+          "${cfg.package}/bin/wyoming-openwakeword"
+          "--uri ${cfg.uri}"
+          (concatMapStringsSep " " (model: "--preload-model ${model}") cfg.preloadModels)
+          (concatMapStringsSep " " (dir: "--custom-model-dir ${toString dir}") cfg.customModelsDirectories)
+          "--threshold ${cfg.threshold}"
+          "--trigger-level ${cfg.triggerLevel}"
+          "${cfg.extraArgs}"
+        ];
+        CapabilityBoundingSet = "";
+        DeviceAllow = "";
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectProc = "invisible";
+        ProcSubset = "all"; # reads /proc/cpuinfo
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RuntimeDirectory = "wyoming-openwakeword";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/wyoming/piper.nix b/nixos/modules/services/audio/wyoming/piper.nix
index ed50bd9f48e95..2828fdf078921 100644
--- a/nixos/modules/services/audio/wyoming/piper.nix
+++ b/nixos/modules/services/audio/wyoming/piper.nix
@@ -12,7 +12,7 @@ let
     mkOption
     mdDoc
     mkEnableOption
-    mkPackageOptionMD
+    mkPackageOption
     types
     ;
 
@@ -26,7 +26,7 @@ in
   meta.buildDocsInSandbox = false;
 
   options.services.wyoming.piper = with types; {
-    package = mkPackageOptionMD pkgs "wyoming-piper" { };
+    package = mkPackageOption pkgs "wyoming-piper" { };
 
     servers = mkOption {
       default = {};
@@ -38,7 +38,7 @@ in
           options = {
             enable = mkEnableOption (mdDoc "Wyoming Piper server");
 
-            piper = mkPackageOptionMD pkgs "piper-tts" { };
+            piper = mkPackageOption pkgs "piper-tts" { };
 
             voice = mkOption {
               type = str;
@@ -116,6 +116,7 @@ in
   in mkIf (cfg.servers != {}) {
     systemd.services = mapAttrs' (server: options:
       nameValuePair "wyoming-piper-${server}" {
+        inherit (options) enable;
         description = "Wyoming Piper server instance ${server}";
         after = [
           "network-online.target"
diff --git a/nixos/modules/services/audio/ympd.nix b/nixos/modules/services/audio/ympd.nix
index b74cc3f9c0b41..6e8d22dab3c80 100644
--- a/nixos/modules/services/audio/ympd.nix
+++ b/nixos/modules/services/audio/ympd.nix
@@ -50,6 +50,7 @@ in {
       description = "Standalone MPD Web GUI written in C";
 
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
 
       serviceConfig = {
diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix
index 0acbf1b3eabba..5a75a46e5259a 100644
--- a/nixos/modules/services/backup/bacula.nix
+++ b/nixos/modules/services/backup/bacula.nix
@@ -15,16 +15,16 @@ let
       Client {
         Name = "${fd_cfg.name}";
         FDPort = ${toString fd_cfg.port};
-        WorkingDirectory = "${libDir}";
-        Pid Directory = "/run";
+        WorkingDirectory = ${libDir};
+        Pid Directory = /run;
         ${fd_cfg.extraClientConfig}
       }
 
       ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
       Director {
         Name = "${name}";
-        Password = "${value.password}";
-        Monitor = "${value.monitor}";
+        Password = ${value.password};
+        Monitor = ${value.monitor};
       }
       '') fd_cfg.director)}
 
@@ -41,8 +41,8 @@ let
       Storage {
         Name = "${sd_cfg.name}";
         SDPort = ${toString sd_cfg.port};
-        WorkingDirectory = "${libDir}";
-        Pid Directory = "/run";
+        WorkingDirectory = ${libDir};
+        Pid Directory = /run;
         ${sd_cfg.extraStorageConfig}
       }
 
@@ -50,8 +50,8 @@ let
       Autochanger {
         Name = "${name}";
         Device = ${concatStringsSep ", " (map (a: "\"${a}\"") value.devices)};
-        Changer Device =  "${value.changerDevice}";
-        Changer Command = "${value.changerCommand}";
+        Changer Device =  ${value.changerDevice};
+        Changer Command = ${value.changerCommand};
         ${value.extraAutochangerConfig}
       }
       '') sd_cfg.autochanger)}
@@ -59,8 +59,8 @@ let
       ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
       Device {
         Name = "${name}";
-        Archive Device = "${value.archiveDevice}";
-        Media Type = "${value.mediaType}";
+        Archive Device = ${value.archiveDevice};
+        Media Type = ${value.mediaType};
         ${value.extraDeviceConfig}
       }
       '') sd_cfg.device)}
@@ -68,8 +68,8 @@ let
       ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
       Director {
         Name = "${name}";
-        Password = "${value.password}";
-        Monitor = "${value.monitor}";
+        Password = ${value.password};
+        Monitor = ${value.monitor};
       }
       '') sd_cfg.director)}
 
@@ -85,18 +85,18 @@ let
     ''
     Director {
       Name = "${dir_cfg.name}";
-      Password = "${dir_cfg.password}";
+      Password = ${dir_cfg.password};
       DirPort = ${toString dir_cfg.port};
-      Working Directory = "${libDir}";
-      Pid Directory = "/run/";
-      QueryFile = "${pkgs.bacula}/etc/query.sql";
+      Working Directory = ${libDir};
+      Pid Directory = /run/;
+      QueryFile = ${pkgs.bacula}/etc/query.sql;
       ${dir_cfg.extraDirectorConfig}
     }
 
     Catalog {
-      Name = "PostgreSQL";
-      dbname = "bacula";
-      user = "bacula";
+      Name = PostgreSQL;
+      dbname = bacula;
+      user = bacula;
     }
 
     Messages {
@@ -533,7 +533,7 @@ in {
       };
     };
 
-    services.postgresql.enable = dir_cfg.enable == true;
+    services.postgresql.enable = lib.mkIf dir_cfg.enable true;
 
     systemd.services.bacula-dir = mkIf dir_cfg.enable {
       after = [ "network.target" "postgresql.service" ];
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 3b44f097ab791..6f4455d3be605 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -84,8 +84,8 @@ let
       backupScript = mkBackupScript backupJobName cfg;
     in nameValuePair backupJobName {
       description = "BorgBackup job ${name}";
-      path = with pkgs; [
-        borgbackup openssh
+      path =  [
+        config.services.borgbackup.package pkgs.openssh
       ];
       script = "exec " + optionalString cfg.inhibitsSleep ''\
         ${pkgs.systemd}/bin/systemd-inhibit \
@@ -137,26 +137,21 @@ let
     '');
 
   mkBorgWrapper = name: cfg: mkWrapperDrv {
-    original = "${pkgs.borgbackup}/bin/borg";
+    original = getExe config.services.borgbackup.package;
     name = "borg-job-${name}";
     set = { BORG_REPO = cfg.repo; } // (mkPassEnv cfg) // cfg.environment;
   };
 
   # Paths listed in ReadWritePaths must exist before service is started
-  mkActivationScript = name: cfg:
+  mkTmpfiles = name: cfg:
     let
-      install = "install -o ${cfg.user} -g ${cfg.group}";
-    in
-      nameValuePair "borgbackup-job-${name}" (stringAfter [ "users" ] (''
-        # Ensure that the home directory already exists
-        # We can't assert createHome == true because that's not the case for root
-        cd "${config.users.users.${cfg.user}.home}"
-        # Create each directory separately to prevent root owned parent dirs
-        ${install} -d .config .config/borg
-        ${install} -d .cache .cache/borg
-      '' + optionalString (isLocalPath cfg.repo && !cfg.removableDevice) ''
-        ${install} -d ${escapeShellArg cfg.repo}
-      ''));
+      settings = { inherit (cfg) user group; };
+    in lib.nameValuePair "borgbackup-job-${name}" ({
+      "${config.users.users."${cfg.user}".home}/.config/borg".d = settings;
+      "${config.users.users."${cfg.user}".home}/.cache/borg".d = settings;
+    } // optionalAttrs (isLocalPath cfg.repo && !cfg.removableDevice) {
+      "${cfg.repo}".d = settings;
+    });
 
   mkPassAssertion = name: cfg: {
     assertion = with cfg.encryption;
@@ -231,6 +226,8 @@ in {
 
   ###### interface
 
+  options.services.borgbackup.package = mkPackageOption pkgs "borgbackup" { };
+
   options.services.borgbackup.jobs = mkOption {
     description = lib.mdDoc ''
       Deduplicating backups using BorgBackup.
@@ -600,53 +597,56 @@ in {
           };
 
           extraArgs = mkOption {
-            type = types.str;
+            type = with types; coercedTo (listOf str) escapeShellArgs str;
             description = lib.mdDoc ''
               Additional arguments for all {command}`borg` calls the
               service has. Handle with care.
             '';
-            default = "";
-            example = "--remote-path=/path/to/borg";
+            default = [ ];
+            example = [ "--remote-path=/path/to/borg" ];
           };
 
           extraInitArgs = mkOption {
-            type = types.str;
+            type = with types; coercedTo (listOf str) escapeShellArgs str;
             description = lib.mdDoc ''
               Additional arguments for {command}`borg init`.
               Can also be set at runtime using `$extraInitArgs`.
             '';
-            default = "";
-            example = "--append-only";
+            default = [ ];
+            example = [ "--append-only" ];
           };
 
           extraCreateArgs = mkOption {
-            type = types.str;
+            type = with types; coercedTo (listOf str) escapeShellArgs str;
             description = lib.mdDoc ''
               Additional arguments for {command}`borg create`.
               Can also be set at runtime using `$extraCreateArgs`.
             '';
-            default = "";
-            example = "--stats --checkpoint-interval 600";
+            default = [ ];
+            example = [
+              "--stats"
+              "--checkpoint-interval 600"
+            ];
           };
 
           extraPruneArgs = mkOption {
-            type = types.str;
+            type = with types; coercedTo (listOf str) escapeShellArgs str;
             description = lib.mdDoc ''
               Additional arguments for {command}`borg prune`.
               Can also be set at runtime using `$extraPruneArgs`.
             '';
-            default = "";
-            example = "--save-space";
+            default = [ ];
+            example = [ "--save-space" ];
           };
 
           extraCompactArgs = mkOption {
-            type = types.str;
+            type = with types; coercedTo (listOf str) escapeShellArgs str;
             description = lib.mdDoc ''
               Additional arguments for {command}`borg compact`.
               Can also be set at runtime using `$extraCompactArgs`.
             '';
-            default = "";
-            example = "--cleanup-commits";
+            default = [ ];
+            example = [ "--cleanup-commits" ];
           };
         };
       }
@@ -755,7 +755,7 @@ in {
         ++ mapAttrsToList mkSourceAssertions jobs
         ++ mapAttrsToList mkRemovableDeviceAssertions jobs;
 
-      system.activationScripts = mapAttrs' mkActivationScript jobs;
+      systemd.tmpfiles.settings = mapAttrs' mkTmpfiles jobs;
 
       systemd.services =
         # A job named "foo" is mapped to systemd.services.borgbackup-job-foo
@@ -769,6 +769,7 @@ in {
 
       users = mkMerge (mapAttrsToList mkUsersConfig repos);
 
-      environment.systemPackages = with pkgs; [ borgbackup ] ++ (mapAttrsToList mkBorgWrapper jobs);
+      environment.systemPackages =
+        [ config.services.borgbackup.package ] ++ (mapAttrsToList mkBorgWrapper jobs);
     });
 }
diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix
index 5ee036e68c7bc..b27dd2817120b 100644
--- a/nixos/modules/services/backup/borgmatic.nix
+++ b/nixos/modules/services/backup/borgmatic.nix
@@ -6,32 +6,50 @@ let
   cfg = config.services.borgmatic;
   settingsFormat = pkgs.formats.yaml { };
 
+  repository = with types; submodule {
+    options = {
+      path = mkOption {
+        type = str;
+        description = mdDoc ''
+          Path to the repository
+        '';
+      };
+      label = mkOption {
+        type = str;
+        description = mdDoc ''
+          Label to the repository
+        '';
+      };
+    };
+  };
   cfgType = with types; submodule {
     freeformType = settingsFormat.type;
-    options.location = {
+    options = {
       source_directories = mkOption {
-        type = listOf str;
+        type = nullOr (listOf str);
+        default = null;
         description = mdDoc ''
-          List of source directories to backup (required). Globs and
-          tildes are expanded.
+          List of source directories and files to backup. Globs and tildes are
+          expanded. Do not backslash spaces in path names.
         '';
-        example = [ "/home" "/etc" "/var/log/syslog*" ];
+        example = [ "/home" "/etc" "/var/log/syslog*" "/home/user/path with spaces" ];
       };
       repositories = mkOption {
-        type = listOf str;
+        type = nullOr (listOf repository);
+        default = null;
         description = mdDoc ''
-          Paths to local or remote repositories (required). Tildes are
-          expanded. Multiple repositories are backed up to in
-          sequence. Borg placeholders can be used. See the output of
-          "borg help placeholders" for details. See ssh_command for
-          SSH options like identity file or port. If systemd service
-          is used, then add local repository paths in the systemd
-          service file to the ReadWritePaths list.
+          A required list of local or remote repositories with paths and
+          optional labels (which can be used with the --repository flag to
+          select a repository). Tildes are expanded. Multiple repositories are
+          backed up to in sequence. Borg placeholders can be used. See the
+          output of "borg help placeholders" for details. See ssh_command for
+          SSH options like identity file or port. If systemd service is used,
+          then add local repository paths in the systemd service file to the
+          ReadWritePaths list.
         '';
         example = [
-          "ssh://user@backupserver/./sourcehostname.borg"
-          "ssh://user@backupserver/./{fqdn}"
-          "/var/local/backups/local.borg"
+          { path="ssh://user@backupserver/./sourcehostname.borg"; label="backupserver"; }
+          { path="/mnt/backup"; label="local"; }
         ];
       };
     };
@@ -62,6 +80,13 @@ in
 
   config = mkIf cfg.enable {
 
+    warnings = []
+      ++ optional (cfg.settings != null && cfg.settings ? location)
+        "`services.borgmatic.settings.location` is deprecated, please move your options out of sections to the global scope"
+      ++ optional (catAttrs "location" (attrValues cfg.configurations) != [])
+        "`services.borgmatic.configurations.<name>.location` is deprecated, please move your options out of sections to the global scope"
+    ;
+
     environment.systemPackages = [ pkgs.borgmatic ];
 
     environment.etc = (optionalAttrs (cfg.settings != null) { "borgmatic/config.yaml".source = cfgfile; }) //
diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index b838c174553d2..364b77b6a21c1 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -6,14 +6,17 @@ let
     concatMapStringsSep
     concatStringsSep
     filterAttrs
+    flatten
+    getAttr
     isAttrs
     literalExpression
     mapAttrs'
     mapAttrsToList
     mkIf
     mkOption
+    optional
     optionalString
-    sort
+    sortOn
     types
     ;
 
@@ -37,7 +40,7 @@ let
   genConfig = set:
     let
       pairs = mapAttrsToList (name: value: { inherit name value; }) set;
-      sortedPairs = sort (a: b: prioOf a < prioOf b) pairs;
+      sortedPairs = sortOn prioOf pairs;
     in
       concatMap genPair sortedPairs;
   genSection = sec: secName: value:
@@ -47,8 +50,21 @@ let
     then [ "${name} ${value}" ]
     else concatLists (mapAttrsToList (genSection name) value);
 
+  sudoRule = {
+    users = [ "btrbk" ];
+    commands = [
+      { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; }
+      { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
+      { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
+      # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
+      { command = "/run/current-system/sw/bin/btrfs"; options = [ "NOPASSWD" ]; }
+      { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
+      { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
+    ];
+  };
+
   sudo_doas =
-    if config.security.sudo.enable then "sudo"
+    if config.security.sudo.enable || config.security.sudo-rs.enable then "sudo"
     else if config.security.doas.enable then "doas"
     else throw "The btrbk nixos module needs either sudo or doas enabled in the configuration";
 
@@ -71,6 +87,18 @@ let
     '';
   };
 
+  streamCompressMap = {
+    gzip = pkgs.gzip;
+    pigz = pkgs.pigz;
+    bzip2 = pkgs.bzip2;
+    pbzip2 = pkgs.pbzip2;
+    bzip3 = pkgs.bzip3;
+    xz = pkgs.xz;
+    lzo = pkgs.lzo;
+    lz4 = pkgs.lz4;
+    zstd = pkgs.zstd;
+  };
+
   cfg = config.services.btrbk;
   sshEnabled = cfg.sshAccess != [ ];
   serviceEnabled = cfg.instances != { };
@@ -81,7 +109,14 @@ in
   options = {
     services.btrbk = {
       extraPackages = mkOption {
-        description = lib.mdDoc "Extra packages for btrbk, like compression utilities for `stream_compress`";
+        description = lib.mdDoc ''
+          Extra packages for btrbk, like compression utilities for `stream_compress`.
+
+          **Note**: This option will get deprecated in future releases.
+          Required compression programs will get automatically provided to btrbk
+          depending on configured compression method in
+          `services.btrbk.instances.<name>.settings` option.
+        '';
         type = types.listOf types.package;
         default = [ ];
         example = literalExpression "[ pkgs.xz ]";
@@ -111,7 +146,19 @@ in
                   '';
                 };
                 settings = mkOption {
-                  type = let t = types.attrsOf (types.either types.str (t // { description = "instances of this type recursively"; })); in t;
+                  type = types.submodule {
+                    freeformType = let t = types.attrsOf (types.either types.str (t // { description = "instances of this type recursively"; })); in t;
+                    options = {
+                      stream_compress = mkOption {
+                        description = lib.mdDoc ''
+                          Compress the btrfs send stream before transferring it from/to remote locations using a
+                          compression command.
+                        '';
+                        type = types.enum ["gzip" "pigz" "bzip2" "pbzip2" "bzip3" "xz" "lzo" "lz4" "zstd" "no"];
+                        default = "no";
+                      };
+                    };
+                  };
                   default = { };
                   example = {
                     snapshot_preserve_min = "2d";
@@ -156,23 +203,16 @@ in
 
   };
   config = mkIf (sshEnabled || serviceEnabled) {
+
+    warnings = optional (cfg.extraPackages != []) ''
+      extraPackages option will be deprecated in future releases. Programs required for compression are now automatically selected depending on services.btrbk.instances.<name>.settings.stream_compress option.
+    '';
+
     environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages;
-    security.sudo = mkIf (sudo_doas == "sudo") {
-      extraRules = [
-        {
-            users = [ "btrbk" ];
-            commands = [
-            { command = "${pkgs.btrfs-progs}/bin/btrfs"; options = [ "NOPASSWD" ]; }
-            { command = "${pkgs.coreutils}/bin/mkdir"; options = [ "NOPASSWD" ]; }
-            { command = "${pkgs.coreutils}/bin/readlink"; options = [ "NOPASSWD" ]; }
-            # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
-            { command = "/run/current-system/bin/btrfs"; options = [ "NOPASSWD" ]; }
-            { command = "/run/current-system/sw/bin/mkdir"; options = [ "NOPASSWD" ]; }
-            { command = "/run/current-system/sw/bin/readlink"; options = [ "NOPASSWD" ]; }
-            ];
-        }
-      ];
-    };
+
+    security.sudo.extraRules = mkIf (sudo_doas == "sudo") [ sudoRule ];
+    security.sudo-rs.extraRules = mkIf (sudo_doas == "sudo") [ sudoRule ];
+
     security.doas = mkIf (sudo_doas == "doas") {
       extraRules = let
         doasCmdNoPass = cmd: { users = [ "btrbk" ]; cmd = cmd; noPass = true; };
@@ -182,7 +222,7 @@ in
             (doasCmdNoPass "${pkgs.coreutils}/bin/mkdir")
             (doasCmdNoPass "${pkgs.coreutils}/bin/readlink")
             # for ssh, they are not the same than the one hard coded in ${pkgs.btrbk}
-            (doasCmdNoPass "/run/current-system/bin/btrfs")
+            (doasCmdNoPass "/run/current-system/sw/bin/btrfs")
             (doasCmdNoPass "/run/current-system/sw/bin/mkdir")
             (doasCmdNoPass "/run/current-system/sw/bin/readlink")
 
@@ -231,12 +271,15 @@ in
       cfg.instances;
     systemd.services = mapAttrs'
       (
-        name: _: {
+        name: instance: {
           name = "btrbk-${name}";
           value = {
             description = "Takes BTRFS snapshots and maintains retention policies.";
             unitConfig.Documentation = "man:btrbk(1)";
-            path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+            path = [ "/run/wrappers" ]
+              ++ cfg.extraPackages
+              ++ optional (instance.settings.stream_compress != "no")
+                (getAttr instance.settings.stream_compress streamCompressMap);
             serviceConfig = {
               User = "btrbk";
               Group = "btrbk";
diff --git a/nixos/modules/services/backup/duplicati.nix b/nixos/modules/services/backup/duplicati.nix
index 007396ebfc9b4..bd433b777ec49 100644
--- a/nixos/modules/services/backup/duplicati.nix
+++ b/nixos/modules/services/backup/duplicati.nix
@@ -10,6 +10,8 @@ in
     services.duplicati = {
       enable = mkEnableOption (lib.mdDoc "Duplicati");
 
+      package = mkPackageOption pkgs "duplicati" { };
+
       port = mkOption {
         default = 8200;
         type = types.port;
@@ -53,7 +55,7 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.duplicati ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services.duplicati = {
       description = "Duplicati backup";
@@ -63,7 +65,7 @@ in
         {
           User = cfg.user;
           Group = "duplicati";
-          ExecStart = "${pkgs.duplicati}/bin/duplicati-server --webservice-interface=${cfg.interface} --webservice-port=${toString cfg.port} --server-datafolder=${cfg.dataDir}";
+          ExecStart = "${cfg.package}/bin/duplicati-server --webservice-interface=${cfg.interface} --webservice-port=${toString cfg.port} --server-datafolder=${cfg.dataDir}";
           Restart = "on-failure";
         }
         (mkIf (cfg.dataDir == "/var/lib/duplicati") {
@@ -83,4 +85,3 @@ in
 
   };
 }
-
diff --git a/nixos/modules/services/backup/postgresql-backup.nix b/nixos/modules/services/backup/postgresql-backup.nix
index d3c6f3104fc54..82067d8ade34d 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 -${toString cfg.compressionLevel}";
-        "zstd" = "${pkgs.zstd}/bin/zstd -c -${toString cfg.compressionLevel}";
+        "gzip" = "${pkgs.gzip}/bin/gzip -c -${toString cfg.compressionLevel} --rsyncable";
+        "zstd" = "${pkgs.zstd}/bin/zstd -c -${toString cfg.compressionLevel} --rsyncable";
       };
 
       mkSqlPath = prefix: suffix: "${cfg.location}/${db}${prefix}.sql${suffix}";
@@ -178,4 +178,5 @@ in {
     })
   ];
 
+  meta.maintainers = with lib.maintainers; [ Scrumplex ];
 }
diff --git a/nixos/modules/services/backup/postgresql-wal-receiver.nix b/nixos/modules/services/backup/postgresql-wal-receiver.nix
index 01fd57f5c5062..332a32d37052e 100644
--- a/nixos/modules/services/backup/postgresql-wal-receiver.nix
+++ b/nixos/modules/services/backup/postgresql-wal-receiver.nix
@@ -5,12 +5,8 @@ with lib;
 let
   receiverSubmodule = {
     options = {
-      postgresqlPackage = mkOption {
-        type = types.package;
-        example = literalExpression "pkgs.postgresql_11";
-        description = lib.mdDoc ''
-          PostgreSQL package to use.
-        '';
+      postgresqlPackage = mkPackageOption pkgs "postgresql" {
+        example = "postgresql_15";
       };
 
       directory = mkOption {
@@ -124,7 +120,7 @@ in {
         example = literalExpression ''
           {
             main = {
-              postgresqlPackage = pkgs.postgresql_11;
+              postgresqlPackage = pkgs.postgresql_15;
               directory = /mnt/pg_wal/main/;
               slot = "main_wal_receiver";
               connection = "postgresql://user@somehost";
diff --git a/nixos/modules/services/backup/restic-rest-server.nix b/nixos/modules/services/backup/restic-rest-server.nix
index 37a6150c99d3e..105a05caf3048 100644
--- a/nixos/modules/services/backup/restic-rest-server.nix
+++ b/nixos/modules/services/backup/restic-rest-server.nix
@@ -57,12 +57,7 @@ in
       '';
     };
 
-    package = mkOption {
-      default = pkgs.restic-rest-server;
-      defaultText = literalExpression "pkgs.restic-rest-server";
-      type = types.package;
-      description = lib.mdDoc "Restic REST server package to use.";
-    };
+    package = mkPackageOption pkgs "restic-rest-server" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 6f4cbab817267..b222dd952d159 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -23,25 +23,13 @@ in
 
         environmentFile = mkOption {
           type = with types; nullOr str;
-          # added on 2021-08-28, s3CredentialsFile should
-          # be removed in the future (+ remember the warning)
-          default = config.s3CredentialsFile;
+          default = null;
           description = lib.mdDoc ''
             file containing the credentials to access the repository, in the
             format of an EnvironmentFile as described by systemd.exec(5)
           '';
         };
 
-        s3CredentialsFile = mkOption {
-          type = with types; nullOr str;
-          default = null;
-          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)
-          '';
-        };
-
         rcloneOptions = mkOption {
           type = with types; nullOr (attrsOf (oneOf [ str bool ]));
           default = null;
@@ -113,12 +101,15 @@ in
         };
 
         paths = mkOption {
+          # This is nullable for legacy reasons only. We should consider making it a pure listOf
+          # after some time has passed since this comment was added.
           type = types.nullOr (types.listOf types.str);
-          default = null;
+          default = [ ];
           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.
+            Which paths to backup, in addition to ones specified via
+            `dynamicFilesFrom`.  If null or an empty array and
+            `dynamicFilesFrom` is also null, no backup command will be run.
+             This can be used to create a prune-only job.
           '';
           example = [
             "/var/lib/postgresql"
@@ -142,13 +133,15 @@ in
         };
 
         timerConfig = mkOption {
-          type = types.attrsOf unitOption;
+          type = types.nullOr (types.attrsOf unitOption);
           default = {
             OnCalendar = "daily";
             Persistent = true;
           };
           description = lib.mdDoc ''
-            When to run the backup. See {manpage}`systemd.timer(5)` for details.
+            When to run the backup. See {manpage}`systemd.timer(5)` for
+            details. If null no timer is created and the backup will only
+            run when explicitly started.
           '';
           example = {
             OnCalendar = "00:05";
@@ -231,7 +224,7 @@ in
           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.
+            option. The result is merged with paths specified via `paths`.
           '';
           example = "find /home/matt/git -type d -name .git";
         };
@@ -252,12 +245,15 @@ in
           '';
         };
 
-        package = mkOption {
-          type = types.package;
-          default = pkgs.restic;
-          defaultText = literalExpression "pkgs.restic";
-          description = lib.mdDoc ''
-            Restic package to use.
+        package = mkPackageOption pkgs "restic" { };
+
+        createWrapper = lib.mkOption {
+          type = lib.types.bool;
+          default = true;
+          description = ''
+            Whether to generate and add a script to the system path, that has the same environment variables set
+            as the systemd service. This can be used to e.g. mount snapshots or perform other opterations, without
+            having to manually specify most options.
           '';
         };
       };
@@ -287,7 +283,6 @@ in
   };
 
   config = {
-    warnings = mapAttrsToList (n: v: "services.restic.backups.${n}.s3CredentialsFile is deprecated, please use services.restic.backups.${n}.environmentFile instead.") (filterAttrs (n: v: v.s3CredentialsFile != null) config.services.restic.backups);
     assertions = mapAttrsToList (n: v: {
       assertion = (v.repository == null) != (v.repositoryFile == null);
       message = "services.restic.backups.${n}: exactly one of repository or repositoryFile should be set";
@@ -300,10 +295,7 @@ in
             resticCmd = "${backup.package}/bin/restic${extraOptions}";
             excludeFlags = optional (backup.exclude != []) "--exclude-file=${pkgs.writeText "exclude-patterns" (concatStringsSep "\n" backup.exclude)}";
             filesFromTmpFile = "/run/restic-backups-${name}/includes";
-            backupPaths =
-              if (backup.dynamicFilesFrom == null)
-              then optionalString (backup.paths != null) (concatStringsSep " " backup.paths)
-              else "--files-from ${filesFromTmpFile}";
+            doBackup = (backup.dynamicFilesFrom != null) || (backup.paths != null && backup.paths != []);
             pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
               (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts))
               (resticCmd + " check " + (concatStringsSep " " backup.checkOpts))
@@ -316,7 +308,8 @@ in
           in
           nameValuePair "restic-backups-${name}" ({
             environment = {
-              RESTIC_CACHE_DIR = "%C/restic-backups-${name}";
+              # not %C, because that wouldn't work in the wrapper script
+              RESTIC_CACHE_DIR = "/var/cache/restic-backups-${name}";
               RESTIC_PASSWORD_FILE = backup.passwordFile;
               RESTIC_REPOSITORY = backup.repository;
               RESTIC_REPOSITORY_FILE = backup.repositoryFile;
@@ -331,13 +324,13 @@ in
                 nameValuePair (rcloneAttrToConf name) (toRcloneVal value)
               )
               backup.rcloneConfig);
-            path = [ pkgs.openssh ];
+            path = [ config.programs.ssh.package ];
             restartIfChanged = false;
             wants = [ "network-online.target" ];
             after = [ "network-online.target" ];
             serviceConfig = {
               Type = "oneshot";
-              ExecStart = (optionals (backupPaths != "") [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} ${backupPaths}" ])
+              ExecStart = (optionals doBackup [ "${resticCmd} backup ${concatStringsSep " " (backup.extraBackupArgs ++ excludeFlags)} --files-from=${filesFromTmpFile}" ])
                 ++ pruneCmd;
               User = backup.user;
               RuntimeDirectory = "restic-backups-${name}";
@@ -347,7 +340,7 @@ in
             } // optionalAttrs (backup.environmentFile != null) {
               EnvironmentFile = backup.environmentFile;
             };
-          } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null || backup.backupPrepareCommand != null) {
+          } // optionalAttrs (backup.initialize || doBackup || backup.backupPrepareCommand != null) {
             preStart = ''
               ${optionalString (backup.backupPrepareCommand != null) ''
                 ${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
@@ -355,16 +348,19 @@ in
               ${optionalString (backup.initialize) ''
                 ${resticCmd} snapshots || ${resticCmd} init
               ''}
+              ${optionalString (backup.paths != null && backup.paths != []) ''
+                cat ${pkgs.writeText "staticPaths" (concatStringsSep "\n" backup.paths)} >> ${filesFromTmpFile}
+              ''}
               ${optionalString (backup.dynamicFilesFrom != null) ''
-                ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile}
+                ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
               ''}
             '';
-          } // optionalAttrs (backup.dynamicFilesFrom != null || backup.backupCleanupCommand != null) {
+          } // optionalAttrs (doBackup || backup.backupCleanupCommand != null) {
             postStop = ''
               ${optionalString (backup.backupCleanupCommand != null) ''
                 ${pkgs.writeScript "backupCleanupCommand" backup.backupCleanupCommand}
               ''}
-              ${optionalString (backup.dynamicFilesFrom != null) ''
+              ${optionalString doBackup ''
                 rm ${filesFromTmpFile}
               ''}
             '';
@@ -377,6 +373,24 @@ in
           wantedBy = [ "timers.target" ];
           timerConfig = backup.timerConfig;
         })
-        config.services.restic.backups;
+        (filterAttrs (_: backup: backup.timerConfig != null) config.services.restic.backups);
+
+    # generate wrapper scripts, as described in the createWrapper option
+    environment.systemPackages = lib.mapAttrsToList (name: backup: let
+      extraOptions = lib.concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
+      resticCmd = "${backup.package}/bin/restic${extraOptions}";
+    in pkgs.writeShellScriptBin "restic-${name}" ''
+      set -a  # automatically export variables
+      ${lib.optionalString (backup.environmentFile != null) "source ${backup.environmentFile}"}
+      # set same environment variables as the systemd service
+      ${lib.pipe config.systemd.services."restic-backups-${name}".environment [
+        (lib.filterAttrs (n: v: v != null && n != "PATH"))
+        (lib.mapAttrsToList (n: v: "${n}=${v}"))
+        (lib.concatStringsSep "\n")
+      ]}
+      PATH=${config.systemd.services."restic-backups-${name}".environment.PATH}:$PATH
+
+      exec ${resticCmd} $@
+    '') (lib.filterAttrs (_: v: v.createWrapper) config.services.restic.backups);
   };
 }
diff --git a/nixos/modules/services/backup/sanoid.nix b/nixos/modules/services/backup/sanoid.nix
index aae77cee07d0f..46d1de4ed934d 100644
--- a/nixos/modules/services/backup/sanoid.nix
+++ b/nixos/modules/services/backup/sanoid.nix
@@ -114,7 +114,7 @@ in
   options.services.sanoid = {
     enable = mkEnableOption (lib.mdDoc "Sanoid ZFS snapshotting service");
 
-    package = lib.mkPackageOptionMD pkgs "sanoid" {};
+    package = lib.mkPackageOption pkgs "sanoid" {};
 
     interval = mkOption {
       type = types.str;
diff --git a/nixos/modules/tasks/snapraid.nix b/nixos/modules/services/backup/snapraid.nix
index 243d25f884237..c9b2550e80e81 100644
--- a/nixos/modules/tasks/snapraid.nix
+++ b/nixos/modules/services/backup/snapraid.nix
@@ -2,10 +2,15 @@
 
 with lib;
 
-let cfg = config.snapraid;
+let cfg = config.services.snapraid;
 in
 {
-  options.snapraid = with types; {
+  imports = [
+    # Should have never been on the top-level.
+    (mkRenamedOptionModule [ "snapraid" ] [ "services" "snapraid" ])
+  ];
+
+  options.services.snapraid = with types; {
     enable = mkEnableOption (lib.mdDoc "SnapRAID");
     dataDisks = mkOption {
       default = { };
@@ -217,9 +222,13 @@ in
               # to remove them if they are stale
               let
                 contentDirs = map dirOf contentFiles;
+                # Multiple "split" parity files can be specified in a single
+                # "parityFile", separated by a comma.
+                # https://www.snapraid.it/manual#7.1
+                splitParityFiles = map (s: splitString "," s) parityFiles;
               in
               unique (
-                attrValues dataDisks ++ parityFiles ++ contentDirs
+                attrValues dataDisks ++ splitParityFiles ++ contentDirs
               );
           } // optionalAttrs touchBeforeSync {
             ExecStartPre = "${pkgs.snapraid}/bin/snapraid touch";
diff --git a/nixos/modules/services/backup/syncoid.nix b/nixos/modules/services/backup/syncoid.nix
index 0f375455e7ed1..7b8d3b431309f 100644
--- a/nixos/modules/services/backup/syncoid.nix
+++ b/nixos/modules/services/backup/syncoid.nix
@@ -87,7 +87,7 @@ in
   options.services.syncoid = {
     enable = mkEnableOption (lib.mdDoc "Syncoid ZFS synchronization service");
 
-    package = lib.mkPackageOptionMD pkgs "sanoid" {};
+    package = lib.mkPackageOption pkgs "sanoid" {};
 
     interval = mkOption {
       type = types.str;
@@ -123,9 +123,7 @@ in
     };
 
     sshKey = mkOption {
-      type = types.nullOr types.path;
-      # Prevent key from being copied to store
-      apply = mapNullable toString;
+      type = with types; nullOr (coercedTo path toString str);
       default = null;
       description = lib.mdDoc ''
         SSH private key file to use to login to the remote system. Can be
@@ -205,9 +203,7 @@ in
           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;
+            type = with types; nullOr (coercedTo path toString str);
             description = lib.mdDoc ''
               SSH private key file to use to login to the remote system.
               Defaults to {option}`services.syncoid.sshKey` option.
@@ -369,7 +365,7 @@ in
               PrivateDevices = true;
               PrivateMounts = true;
               PrivateNetwork = mkDefault false;
-              PrivateUsers = true;
+              PrivateUsers = false; # Enabling this breaks on zfs-2.2.0
               ProtectClock = true;
               ProtectControlGroups = true;
               ProtectHome = true;
diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix
index c4de0b16d47d2..6798b18b3af73 100644
--- a/nixos/modules/services/backup/tsm.nix
+++ b/nixos/modules/services/backup/tsm.nix
@@ -3,6 +3,7 @@
 let
 
   inherit (lib.attrsets) hasAttr;
+  inherit (lib.meta) getExe';
   inherit (lib.modules) mkDefault mkIf;
   inherit (lib.options) mkEnableOption mkOption;
   inherit (lib.types) nonEmptyStr nullOr;
@@ -10,7 +11,7 @@ let
   options.services.tsmBackup = {
     enable = mkEnableOption (lib.mdDoc ''
       automatic backups with the
-      IBM Spectrum Protect (Tivoli Storage Manager, TSM) client.
+      IBM Storage Protect (Tivoli Storage Manager, TSM) client.
       This also enables
       {option}`programs.tsmClient.enable`
     '');
@@ -78,10 +79,10 @@ in
   config = mkIf cfg.enable {
     inherit assertions;
     programs.tsmClient.enable = true;
-    programs.tsmClient.servers.${cfg.servername}.passwdDir =
+    programs.tsmClient.servers.${cfg.servername}.passworddir =
       mkDefault "/var/lib/tsm-backup/password";
     systemd.services.tsm-backup = {
-      description = "IBM Spectrum Protect (Tivoli Storage Manager) Backup";
+      description = "IBM Storage Protect (Tivoli Storage Manager) Backup";
       # DSM_LOG needs a trailing slash to have it treated as a directory.
       # `/var/log` would be littered with TSM log files otherwise.
       environment.DSM_LOG = "/var/log/tsm-backup/";
@@ -89,12 +90,12 @@ in
       environment.HOME = "/var/lib/tsm-backup";
       serviceConfig = {
         # for exit status description see
-        # https://www.ibm.com/docs/en/spectrum-protect/8.1.13?topic=clients-client-return-codes
+        # https://www.ibm.com/docs/en/storage-protect/8.1.20?topic=clients-client-return-codes
         SuccessExitStatus = "4 8";
         # The `-se` option must come after the command.
         # The `-optfile` option suppresses a `dsm.opt`-not-found warning.
         ExecStart =
-          "${cfgPrg.wrappedPackage}/bin/dsmc ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null";
+          "${getExe' cfgPrg.wrappedPackage "dsmc"} ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null";
         LogsDirectory = "tsm-backup";
         StateDirectory = "tsm-backup";
         StateDirectoryMode = "0750";
diff --git a/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index 76f147c18affa..2ebe8ad2f69ae 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -359,14 +359,14 @@ in
       };
 
       features.oracleMode = mkEnableOption (lib.mdDoc ''
-        Destroy snapshots one by one instead of using one long argument list.
+        destroying 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.
+        command fails
       '');
       features.recvu = mkEnableOption (lib.mdDoc ''
         recvu feature which uses `-u` on the receiving end to keep the destination
-        filesystem unmounted.
+        filesystem unmounted
       '');
       features.compressed = mkEnableOption (lib.mdDoc ''
         compressed feature which adds the options `-Lce` to
@@ -377,7 +377,7 @@ in
         support and -e is for embedded data support. see
         {manpage}`znapzend(1)`
         and {manpage}`zfs(8)`
-        for more info.
+        for more info
       '');
       features.sendRaw = mkEnableOption (lib.mdDoc ''
         sendRaw feature which adds the options `-w` to the
@@ -386,25 +386,25 @@ in
         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.
+        non-raw snapshots and vice versa
       '');
       features.skipIntermediates = mkEnableOption (lib.mdDoc ''
-        Enable the skipIntermediates feature to send a single increment
+        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.
+        source, so there are no automatic increments to skip
       '');
       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`: instead, go the slower
         way to first list all impacted dataset names, and then query their
-        configs one by one.
+        configs one by one
       '');
       features.zfsGetType = mkEnableOption (lib.mdDoc ''
-        use zfsGetType if your {command}`zfs get` supports a
+        using 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.
@@ -412,7 +412,7 @@ in
         `--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.
+        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 1d3afa3eda053..8475a347429e7 100644
--- a/nixos/modules/services/backup/zrepl.nix
+++ b/nixos/modules/services/backup/zrepl.nix
@@ -13,12 +13,7 @@ in
     services.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";
-      };
+      package = mkPackageOption pkgs "zrepl" { };
 
       settings = mkOption {
         default = { };
diff --git a/nixos/modules/services/blockchain/ethereum/erigon.nix b/nixos/modules/services/blockchain/ethereum/erigon.nix
index 8ebe0fcaff549..b8edee33e7c64 100644
--- a/nixos/modules/services/blockchain/ethereum/erigon.nix
+++ b/nixos/modules/services/blockchain/ethereum/erigon.nix
@@ -13,6 +13,8 @@ in {
     services.erigon = {
       enable = mkEnableOption (lib.mdDoc "Ethereum implementation on the efficiency frontier");
 
+      package = mkPackageOption pkgs "erigon" { };
+
       extraArgs = mkOption {
         type = types.listOf types.str;
         description = lib.mdDoc "Additional arguments passed to Erigon";
@@ -92,7 +94,7 @@ in {
 
       serviceConfig = {
         LoadCredential = "ERIGON_JWT:${cfg.secretJwtPath}";
-        ExecStart = "${pkgs.erigon}/bin/erigon --config ${configFile} --authrpc.jwtsecret=%d/ERIGON_JWT ${lib.escapeShellArgs cfg.extraArgs}";
+        ExecStart = "${cfg.package}/bin/erigon --config ${configFile} --authrpc.jwtsecret=%d/ERIGON_JWT ${lib.escapeShellArgs cfg.extraArgs}";
         DynamicUser = true;
         Restart = "on-failure";
         StateDirectory = "erigon";
diff --git a/nixos/modules/services/blockchain/ethereum/geth.nix b/nixos/modules/services/blockchain/ethereum/geth.nix
index d12516ca2f249..f07dfa4dc7117 100644
--- a/nixos/modules/services/blockchain/ethereum/geth.nix
+++ b/nixos/modules/services/blockchain/ethereum/geth.nix
@@ -135,12 +135,7 @@ let
         default = [];
       };
 
-      package = mkOption {
-        default = pkgs.go-ethereum.geth;
-        defaultText = literalExpression "pkgs.go-ethereum.geth";
-        type = types.package;
-        description = lib.mdDoc "Package to use as Go Ethereum node.";
-      };
+      package = mkPackageOption pkgs [ "go-ethereum" "geth" ] { };
     };
   };
 in
diff --git a/nixos/modules/services/cluster/corosync/default.nix b/nixos/modules/services/cluster/corosync/default.nix
index 7ef17c46b81e3..477ffbcdb7c76 100644
--- a/nixos/modules/services/cluster/corosync/default.nix
+++ b/nixos/modules/services/cluster/corosync/default.nix
@@ -9,12 +9,7 @@ in
   options.services.corosync = {
     enable = mkEnableOption (lib.mdDoc "corosync");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.corosync;
-      defaultText = literalExpression "pkgs.corosync";
-      description = lib.mdDoc "Package that should be used for corosync.";
-    };
+    package = mkPackageOption pkgs "corosync" { };
 
     clusterName = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/cluster/hadoop/default.nix b/nixos/modules/services/cluster/hadoop/default.nix
index 72bf25c211461..6fa91d2f047e5 100644
--- a/nixos/modules/services/cluster/hadoop/default.nix
+++ b/nixos/modules/services/cluster/hadoop/default.nix
@@ -67,16 +67,16 @@ with lib;
     mapredSiteDefault = mkOption {
       default = {
         "mapreduce.framework.name" = "yarn";
-        "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
-        "mapreduce.map.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
-        "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=${cfg.package}/lib/${cfg.package.untarDir}";
+        "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=${cfg.package}";
+        "mapreduce.map.env" = "HADOOP_MAPRED_HOME=${cfg.package}";
+        "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=${cfg.package}";
       };
       defaultText = literalExpression ''
         {
           "mapreduce.framework.name" = "yarn";
-          "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
-          "mapreduce.map.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
-          "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}";
+          "yarn.app.mapreduce.am.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}";
+          "mapreduce.map.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}";
+          "mapreduce.reduce.env" = "HADOOP_MAPRED_HOME=''${config.${opt.package}}";
         }
       '';
       type = types.attrsOf types.anything;
@@ -154,13 +154,13 @@ with lib;
     };
 
     log4jProperties = mkOption {
-      default = "${cfg.package}/lib/${cfg.package.untarDir}/etc/hadoop/log4j.properties";
+      default = "${cfg.package}/etc/hadoop/log4j.properties";
       defaultText = literalExpression ''
-        "''${config.${opt.package}}/lib/''${config.${opt.package}.untarDir}/etc/hadoop/log4j.properties"
+        "''${config.${opt.package}}/etc/hadoop/log4j.properties"
       '';
       type = types.path;
       example = literalExpression ''
-        "''${pkgs.hadoop}/lib/''${pkgs.hadoop.untarDir}/etc/hadoop/log4j.properties";
+        "''${pkgs.hadoop}/etc/hadoop/log4j.properties";
       '';
       description = lib.mdDoc "log4j.properties file added to HADOOP_CONF_DIR";
     };
@@ -199,12 +199,7 @@ with lib;
 
     gatewayRole.enable = mkEnableOption (lib.mdDoc "gateway role for deploying hadoop configs");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.hadoop;
-      defaultText = literalExpression "pkgs.hadoop";
-      description = lib.mdDoc "";
-    };
+    package = mkPackageOption pkgs "hadoop" { };
   };
 
 
diff --git a/nixos/modules/services/cluster/hadoop/hbase.nix b/nixos/modules/services/cluster/hadoop/hbase.nix
index a39da2a84ecad..6801e505db64d 100644
--- a/nixos/modules/services/cluster/hadoop/hbase.nix
+++ b/nixos/modules/services/cluster/hadoop/hbase.nix
@@ -134,12 +134,7 @@ in
 
     hbase = {
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.hbase;
-        defaultText = literalExpression "pkgs.hbase";
-        description = mdDoc "HBase package";
-      };
+      package = mkPackageOption pkgs "hbase" { };
 
       rootdir = mkOption {
         description = mdDoc ''
diff --git a/nixos/modules/services/cluster/hadoop/yarn.nix b/nixos/modules/services/cluster/hadoop/yarn.nix
index 26077f35fdd0d..a49aafbd1dca8 100644
--- a/nixos/modules/services/cluster/hadoop/yarn.nix
+++ b/nixos/modules/services/cluster/hadoop/yarn.nix
@@ -160,7 +160,7 @@ in
           umount /run/wrappers/yarn-nodemanager/cgroup/cpu || true
           rm -rf /run/wrappers/yarn-nodemanager/ || true
           mkdir -p /run/wrappers/yarn-nodemanager/{bin,etc/hadoop,cgroup/cpu}
-          cp ${cfg.package}/lib/${cfg.package.untarDir}/bin/container-executor /run/wrappers/yarn-nodemanager/bin/
+          cp ${cfg.package}/bin/container-executor /run/wrappers/yarn-nodemanager/bin/
           chgrp hadoop /run/wrappers/yarn-nodemanager/bin/container-executor
           chmod 6050 /run/wrappers/yarn-nodemanager/bin/container-executor
           cp ${hadoopConf}/container-executor.cfg /run/wrappers/yarn-nodemanager/etc/hadoop/
diff --git a/nixos/modules/services/cluster/k3s/default.nix b/nixos/modules/services/cluster/k3s/default.nix
index 72b2f992a339f..dc71f1372d7ae 100644
--- a/nixos/modules/services/cluster/k3s/default.nix
+++ b/nixos/modules/services/cluster/k3s/default.nix
@@ -15,12 +15,7 @@ in
   options.services.k3s = {
     enable = mkEnableOption (lib.mdDoc "k3s");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.k3s;
-      defaultText = literalExpression "pkgs.k3s";
-      description = lib.mdDoc "Package that should be used for k3s";
-    };
+    package = mkPackageOption pkgs "k3s" { };
 
     role = mkOption {
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index f5374fc719422..3fb916c769715 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -122,12 +122,7 @@ in {
       type = types.listOf (types.enum ["master" "node"]);
     };
 
-    package = mkOption {
-      description = lib.mdDoc "Kubernetes package to use.";
-      type = types.package;
-      default = pkgs.kubernetes;
-      defaultText = literalExpression "pkgs.kubernetes";
-    };
+    package = mkPackageOption pkgs "kubernetes" { };
 
     kubeconfig = mkKubeConfigOptions "Default kubeconfig";
 
diff --git a/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixos/modules/services/cluster/kubernetes/flannel.nix
index 11c5adc6a8859..dca8996df0831 100644
--- a/nixos/modules/services/cluster/kubernetes/flannel.nix
+++ b/nixos/modules/services/cluster/kubernetes/flannel.nix
@@ -13,6 +13,13 @@ in
   ###### interface
   options.services.kubernetes.flannel = {
     enable = mkEnableOption (lib.mdDoc "flannel networking");
+
+    openFirewallPorts = mkOption {
+      description = lib.mdDoc ''
+        Whether to open the Flannel UDP ports in the firewall on all interfaces.'';
+      type = types.bool;
+      default = true;
+    };
   };
 
   ###### implementation
@@ -38,7 +45,7 @@ in
     };
 
     networking = {
-      firewall.allowedUDPPorts = [
+      firewall.allowedUDPPorts = mkIf cfg.openFirewallPorts [
         8285  # flannel udp
         8472  # flannel vxlan
       ];
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index 38682701ea151..35151ebd6bd7b 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -212,7 +212,7 @@ in
 
     services.certmgr = {
       enable = true;
-      package = pkgs.certmgr-selfsigned;
+      package = pkgs.certmgr;
       svcManager = "command";
       specs =
         let
diff --git a/nixos/modules/services/cluster/pacemaker/default.nix b/nixos/modules/services/cluster/pacemaker/default.nix
index 0f37f4b754fe8..255bb107796f1 100644
--- a/nixos/modules/services/cluster/pacemaker/default.nix
+++ b/nixos/modules/services/cluster/pacemaker/default.nix
@@ -9,12 +9,7 @@ in
   options.services.pacemaker = {
     enable = mkEnableOption (lib.mdDoc "pacemaker");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.pacemaker;
-      defaultText = literalExpression "pkgs.pacemaker";
-      description = lib.mdDoc "Package that should be used for pacemaker.";
-    };
+    package = mkPackageOption pkgs "pacemaker" { };
   };
 
   # implementation
diff --git a/nixos/modules/services/cluster/spark/default.nix b/nixos/modules/services/cluster/spark/default.nix
index bf39c55373328..b3e1ac399ae9f 100644
--- a/nixos/modules/services/cluster/spark/default.nix
+++ b/nixos/modules/services/cluster/spark/default.nix
@@ -69,28 +69,26 @@ with lib;
       confDir = mkOption {
         type = types.path;
         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"'';
+        default = "${cfg.package}/conf";
+        defaultText = literalExpression ''"''${package}/conf"'';
       };
       logDir = mkOption {
         type = types.path;
         description = lib.mdDoc "Spark log directory.";
         default = "/var/log/spark";
       };
-      package = mkOption {
-        type = types.package;
-        description = lib.mdDoc "Spark package.";
-        default = pkgs.spark;
-        defaultText = literalExpression "pkgs.spark";
-        example = literalExpression ''pkgs.spark.overrideAttrs (super: rec {
-          pname = "spark";
-          version = "2.4.4";
+      package = mkPackageOption pkgs "spark" {
+        example = ''
+          spark.overrideAttrs (super: rec {
+            pname = "spark";
+            version = "2.4.4";
 
-          src = pkgs.fetchzip {
-            url    = "mirror://apache/spark/"''${pname}-''${version}/''${pname}-''${version}-bin-without-hadoop.tgz";
-            sha256 = "1a9w5k0207fysgpxx6db3a00fs5hdc2ncx99x4ccy2s0v5ndc66g";
-          };
-        })'';
+            src = pkgs.fetchzip {
+              url    = "mirror://apache/spark/"''${pname}-''${version}/''${pname}-''${version}-bin-without-hadoop.tgz";
+              sha256 = "1a9w5k0207fysgpxx6db3a00fs5hdc2ncx99x4ccy2s0v5ndc66g";
+            };
+          })
+        '';
       };
     };
   };
@@ -113,9 +111,9 @@ with lib;
             Type = "forking";
             User = "spark";
             Group = "spark";
-            WorkingDirectory = "${cfg.package}/lib/${cfg.package.untarDir}";
-            ExecStart = "${cfg.package}/lib/${cfg.package.untarDir}/sbin/start-master.sh";
-            ExecStop  = "${cfg.package}/lib/${cfg.package.untarDir}/sbin/stop-master.sh";
+            WorkingDirectory = "${cfg.package}/";
+            ExecStart = "${cfg.package}/sbin/start-master.sh";
+            ExecStop  = "${cfg.package}/sbin/stop-master.sh";
             TimeoutSec = 300;
             StartLimitBurst=10;
             Restart = "always";
@@ -136,9 +134,9 @@ with lib;
           serviceConfig = {
             Type = "forking";
             User = "spark";
-            WorkingDirectory = "${cfg.package}/lib/${cfg.package.untarDir}";
-            ExecStart = "${cfg.package}/lib/${cfg.package.untarDir}/sbin/start-worker.sh spark://${cfg.worker.master}";
-            ExecStop  = "${cfg.package}/lib/${cfg.package.untarDir}/sbin/stop-worker.sh";
+            WorkingDirectory = "${cfg.package}/";
+            ExecStart = "${cfg.package}/sbin/start-worker.sh spark://${cfg.worker.master}";
+            ExecStop  = "${cfg.package}/sbin/stop-worker.sh";
             TimeoutSec = 300;
             StartLimitBurst=10;
             Restart = "always";
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
index 51475171bf3f9..c2132149a3f5d 100644
--- a/nixos/modules/services/computing/boinc/client.nix
+++ b/nixos/modules/services/computing/boinc/client.nix
@@ -27,14 +27,8 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.boinc;
-        defaultText = literalExpression "pkgs.boinc";
-        example = literalExpression "pkgs.boinc-headless";
-        description = lib.mdDoc ''
-          Which BOINC package to use.
-        '';
+      package = mkPackageOption pkgs "boinc" {
+        example = "boinc-headless";
       };
 
       dataDir = mkOption {
@@ -54,7 +48,7 @@ in
           only the hosts listed in {var}`dataDir`/remote_hosts.cfg will be allowed to
           connect.
 
-          See also: <http://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access>
+          See also: <https://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access>
         '';
       };
 
diff --git a/nixos/modules/services/computing/foldingathome/client.nix b/nixos/modules/services/computing/foldingathome/client.nix
index 1229e5ac987e7..09f31cda769ce 100644
--- a/nixos/modules/services/computing/foldingathome/client.nix
+++ b/nixos/modules/services/computing/foldingathome/client.nix
@@ -20,14 +20,7 @@ in
   options.services.foldingathome = {
     enable = mkEnableOption (lib.mdDoc "Folding@home client");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.fahclient;
-      defaultText = literalExpression "pkgs.fahclient";
-      description = lib.mdDoc ''
-        Which Folding@home client to use.
-      '';
-    };
+    package = mkPackageOption pkgs "fahclient" { };
 
     user = mkOption {
       type = types.nullOr types.str;
@@ -63,7 +56,7 @@ in
       default = [];
       description = lib.mdDoc ''
         Extra startup options for the FAHClient. Run
-        `FAHClient --help` to find all the available options.
+        `fah-client --help` to find all the available options.
       '';
     };
   };
@@ -74,7 +67,7 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       script = ''
-        exec ${cfg.package}/bin/FAHClient ${lib.escapeShellArgs args}
+        exec ${lib.getExe cfg.package} ${lib.escapeShellArgs args}
       '';
       serviceConfig = {
         DynamicUser = true;
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 344c43a429b32..9212fe39fd83f 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.slurm;
   opt = options.services.slurm;
-  # configuration file can be generated by http://slurm.schedmd.com/configurator.html
+  # configuration file can be generated by https://slurm.schedmd.com/configurator.html
 
   defaultUser = "slurm";
 
@@ -131,14 +131,10 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
+      package = mkPackageOption pkgs "slurm" {
+        example = "slurm-full";
+      } // {
         default = pkgs.slurm.override { enableX11 = ! cfg.enableSrunX11; };
-        defaultText = literalExpression "pkgs.slurm";
-        example = literalExpression "pkgs.slurm-full";
-        description = lib.mdDoc ''
-          The package to use for slurm binaries.
-        '';
       };
 
       controlMachine = mkOption {
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index 595374ea1e5b3..9f702b17937cf 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -15,6 +15,7 @@ let
 
   defaultMasterCfg = pkgs.writeText "master.cfg" ''
     from buildbot.plugins import *
+    ${cfg.extraImports}
     factory = util.BuildFactory()
     c = BuildmasterConfig = dict(
      workers       = [${concatStringsSep "," cfg.workers}],
@@ -28,6 +29,7 @@ let
      schedulers    = [ ${concatStringsSep "," cfg.schedulers} ],
      builders      = [ ${concatStringsSep "," cfg.builders} ],
      services      = [ ${concatStringsSep "," cfg.reporters} ],
+     configurators = [ ${concatStringsSep "," cfg.configurators} ],
     )
     for step in [ ${concatStringsSep "," cfg.factorySteps} ]:
       factory.addStep(step)
@@ -79,6 +81,15 @@ in {
         ];
       };
 
+      configurators = mkOption {
+        type = types.listOf types.str;
+        description = lib.mdDoc "Configurator Steps, see https://docs.buildbot.net/latest/manual/configuration/configurators.html";
+        default = [];
+        example = [
+          "util.JanitorConfigurator(logHorizon=timedelta(weeks=4), hour=12, dayOfWeek=6)"
+        ];
+      };
+
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -91,6 +102,13 @@ in {
         default = "c['buildbotNetUsageData'] = None";
       };
 
+      extraImports = mkOption {
+        type = types.str;
+        description = lib.mdDoc "Extra python imports to prepend to master.cfg";
+        default = "";
+        example = "from buildbot.process.project import Project";
+      };
+
       masterCfg = mkOption {
         type = types.path;
         description = lib.mdDoc "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
@@ -211,12 +229,8 @@ in {
         description = lib.mdDoc "Specifies port number on which the buildbot HTTP interface listens.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.buildbot-full;
-        defaultText = literalExpression "pkgs.buildbot-full";
-        description = lib.mdDoc "Package to use for buildbot.";
-        example = literalExpression "pkgs.buildbot";
+      package = mkPackageOption pkgs "buildbot-full" {
+        example = "buildbot";
       };
 
       packages = mkOption {
@@ -253,7 +267,7 @@ in {
 
     systemd.services.buildbot-master = {
       description = "Buildbot Continuous Integration Server.";
-      after = [ "network-online.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = cfg.packages ++ cfg.pythonPackages python.pkgs;
       environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ package ])}/${python.sitePackages}";
@@ -272,7 +286,13 @@ in {
         Group = cfg.group;
         WorkingDirectory = cfg.home;
         # NOTE: call twistd directly with stdout logging for systemd
-        ExecStart = "${python.pkgs.twisted}/bin/twistd -o --nodaemon --pidfile= --logfile - --python ${tacFile}";
+        ExecStart = "${python.pkgs.twisted}/bin/twistd -o --nodaemon --pidfile= --logfile - --python ${cfg.buildbotDir}/buildbot.tac";
+        # To reload on upgrade, set the following in your configuration:
+        # systemd.services.buildbot-master.reloadIfChanged = true;
+        ExecReload = [
+          "${pkgs.coreutils}/bin/ln -sf ${tacFile} ${cfg.buildbotDir}/buildbot.tac"
+          "${pkgs.coreutils}/bin/kill -HUP $MAINPID"
+        ];
       };
     };
   };
@@ -285,5 +305,5 @@ in {
     '')
   ];
 
-  meta.maintainers = with lib.maintainers; [ mic92 lopsided98 ];
+  meta.maintainers = lib.teams.buildbot.members;
 }
diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix
index 7e78b8935f81c..9c7b2bdd06e02 100644
--- a/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -128,13 +128,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.buildbot-worker;
-        defaultText = literalExpression "pkgs.python3Packages.buildbot-worker";
-        description = lib.mdDoc "Package to use for buildbot worker.";
-        example = literalExpression "pkgs.python2Packages.buildbot-worker";
-      };
+      package = mkPackageOption pkgs "buildbot-worker" { };
 
       packages = mkOption {
         default = with pkgs; [ git ];
@@ -194,6 +188,6 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = lib.teams.buildbot.members;
 
 }
diff --git a/nixos/modules/services/continuous-integration/buildkite-agents.nix b/nixos/modules/services/continuous-integration/buildkite-agents.nix
index a35ca4168074f..2e488f83d4c3b 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agents.nix
+++ b/nixos/modules/services/continuous-integration/buildkite-agents.nix
@@ -35,6 +35,12 @@ let
         type = lib.types.str;
       };
 
+      extraGroups = lib.mkOption {
+        default = [ "keys" ];
+        description = lib.mdDoc "Groups the user for this buildkite agent should belong to";
+        type = lib.types.listOf lib.types.str;
+      };
+
       runtimePackages = lib.mkOption {
         default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ];
         defaultText = lib.literalExpression "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
@@ -150,7 +156,7 @@ in
       home = cfg.dataDir;
       createHome = true;
       description = "Buildkite agent user";
-      extraGroups = [ "keys" ];
+      extraGroups = cfg.extraGroups;
       isSystemUser = true;
       group = "buildkite-agent-${name}";
     };
diff --git a/nixos/modules/services/continuous-integration/gitea-actions-runner.nix b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
index d8d25898e294b..06f0da3451a6c 100644
--- a/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
@@ -19,7 +19,7 @@ let
     mapAttrs'
     mkEnableOption
     mkOption
-    mkPackageOptionMD
+    mkPackageOption
     mkIf
     nameValuePair
     types
@@ -56,7 +56,7 @@ in
   ];
 
   options.services.gitea-actions-runner = with types; {
-    package = mkPackageOptionMD pkgs "gitea-actions-runner" { };
+    package = mkPackageOption pkgs "gitea-actions-runner" { };
 
     instances = mkOption {
       default = {};
@@ -188,6 +188,7 @@ in
         nameValuePair "gitea-runner-${escapeSystemdPath name}" {
           inherit (instance) enable;
           description = "Gitea Actions Runner";
+          wants = [ "network-online.target" ];
           after = [
             "network-online.target"
           ] ++ optionals (wantsDocker) [
diff --git a/nixos/modules/services/continuous-integration/github-runner/options.nix b/nixos/modules/services/continuous-integration/github-runner/options.nix
index f2887c7711b3b..b9b1ea05e9672 100644
--- a/nixos/modules/services/continuous-integration/github-runner/options.nix
+++ b/nixos/modules/services/continuous-integration/github-runner/options.nix
@@ -153,6 +153,7 @@ with lib;
     type = types.attrs;
     description = lib.mdDoc ''
       Modify the systemd service. Can be used to, e.g., adjust the sandboxing options.
+      See {manpage}`systemd.exec(5)` for more options.
     '';
     example = {
       ProtectHome = false;
@@ -161,14 +162,7 @@ with lib;
     default = {};
   };
 
-  package = mkOption {
-    type = types.package;
-    description = lib.mdDoc ''
-      Which github-runner derivation to use.
-    '';
-    default = pkgs.github-runner;
-    defaultText = literalExpression "pkgs.github-runner";
-  };
+  package = mkPackageOption pkgs "github-runner" { };
 
   ephemeral = mkOption {
     type = types.bool;
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 10a2fe8a44ddc..05b2449936bcd 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -195,12 +195,8 @@ in {
         Time to wait until a graceful shutdown is turned into a forceful one.
       '';
     };
-    package = mkOption {
-      type = types.package;
-      default = pkgs.gitlab-runner;
-      defaultText = literalExpression "pkgs.gitlab-runner";
-      example = literalExpression "pkgs.gitlab-runner_1_11";
-      description = lib.mdDoc "Gitlab Runner package to use.";
+    package = mkPackageOption pkgs "gitlab-runner" {
+      example = "gitlab-runner_1_11";
     };
     extraPackages = mkOption {
       type = types.listOf types.package;
diff --git a/nixos/modules/services/continuous-integration/hail.nix b/nixos/modules/services/continuous-integration/hail.nix
deleted file mode 100644
index 62e8b8077c079..0000000000000
--- a/nixos/modules/services/continuous-integration/hail.nix
+++ /dev/null
@@ -1,61 +0,0 @@
-{ config, lib, pkgs, ...}:
-
-with lib;
-
-let
-  cfg = config.services.hail;
-in {
-
-
-  ###### interface
-
-  options.services.hail = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = lib.mdDoc ''
-        Enables the Hail Auto Update Service. Hail can automatically deploy artifacts
-        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 = lib.mdDoc "The name of the Nix profile used by Hail.";
-    };
-    hydraJobUri = mkOption {
-      type = types.str;
-      description = lib.mdDoc "The URI of the Hydra Job.";
-    };
-    netrc = mkOption {
-      type = types.nullOr types.path;
-      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 = lib.mdDoc "Hail package to use.";
-    };
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-    systemd.services.hail = {
-      description = "Hail Auto Update Service";
-      wants = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-      path = with pkgs; [ nix ];
-      environment = {
-        HOME = "/var/lib/empty";
-      };
-      serviceConfig = {
-        ExecStart = "${cfg.package}/bin/hail --profile ${cfg.profile} --job-uri ${cfg.hydraJobUri}"
-          + lib.optionalString (cfg.netrc != null) " --netrc-file ${cfg.netrc}";
-      };
-    };
-  };
-}
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 ea9b5ffbf43c6..7d33989044deb 100644
--- a/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
+++ b/nixos/modules/services/continuous-integration/hercules-ci-agent/common.nix
@@ -16,7 +16,7 @@ let
     mkRemovedOptionModule
     mkRenamedOptionModule
     types
-
+    mkPackageOption
     ;
 
   cfg = config.services.hercules-ci-agent;
@@ -45,14 +45,7 @@ in
         Support is available at [help@hercules-ci.com](mailto:help@hercules-ci.com).
       '';
     };
-    package = mkOption {
-      description = lib.mdDoc ''
-        Package containing the bin/hercules-ci-agent executable.
-      '';
-      type = types.package;
-      default = pkgs.hercules-ci-agent;
-      defaultText = literalExpression "pkgs.hercules-ci-agent";
-    };
+    package = mkPackageOption pkgs "hercules-ci-agent" { };
     settings = mkOption {
       description = lib.mdDoc ''
         These settings are written to the `agent.toml` file.
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index 83078706fcaec..54bbe69703f95 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -97,12 +97,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.hydra_unstable;
-        defaultText = literalExpression "pkgs.hydra_unstable";
-        description = lib.mdDoc "The Hydra package.";
-      };
+      package = mkPackageOption pkgs "hydra_unstable" { };
 
       hydraURL = mkOption {
         type = types.str;
@@ -398,6 +393,7 @@ in
     systemd.services.hydra-evaluator =
       { wantedBy = [ "multi-user.target" ];
         requires = [ "hydra-init.service" ];
+        wants = [ "network-online.target" ];
         after = [ "hydra-init.service" "network.target" "network-online.target" ];
         path = with pkgs; [ hydra-package nettools jq ];
         restartTriggers = [ hydraConf ];
diff --git a/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixos/modules/services/continuous-integration/jenkins/default.nix
index e4d54b0cb0f49..d69cf4587aaba 100644
--- a/nixos/modules/services/continuous-integration/jenkins/default.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -79,12 +79,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        default = pkgs.jenkins;
-        defaultText = literalExpression "pkgs.jenkins";
-        type = types.package;
-        description = lib.mdDoc "Jenkins package to use.";
-      };
+      package = mkPackageOption pkgs "jenkins" { };
 
       packages = mkOption {
         default = [ pkgs.stdenv pkgs.git pkgs.jdk17 config.programs.ssh.package pkgs.nix ];
@@ -241,6 +236,7 @@ in {
 
       serviceConfig = {
         User = cfg.user;
+        StateDirectory = mkIf (hasPrefix "/var/lib/jenkins" cfg.home) "jenkins";
       };
     };
   };
diff --git a/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixos/modules/services/continuous-integration/jenkins/slave.nix
index 9b86917ab380f..82d34a058c575 100644
--- a/nixos/modules/services/continuous-integration/jenkins/slave.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/slave.nix
@@ -47,14 +47,7 @@ in {
         '';
       };
 
-      javaPackage = mkOption {
-        default = pkgs.jdk;
-        defaultText = literalExpression "pkgs.jdk";
-        description = lib.mdDoc ''
-          Java package to install.
-        '';
-        type = types.package;
-      };
+      javaPackage = mkPackageOption pkgs "jdk" { };
     };
   };
 
diff --git a/nixos/modules/services/continuous-integration/woodpecker/agents.nix b/nixos/modules/services/continuous-integration/woodpecker/agents.nix
index cc5b903afd595..ef7bf3fd2a6e1 100644
--- a/nixos/modules/services/continuous-integration/woodpecker/agents.nix
+++ b/nixos/modules/services/continuous-integration/woodpecker/agents.nix
@@ -11,7 +11,7 @@ let
     options = {
       enable = lib.mkEnableOption (lib.mdDoc "this Woodpecker-Agent. Agents execute tasks generated by a Server, every install will need one server and at least one agent");
 
-      package = lib.mkPackageOptionMD pkgs "woodpecker-agent" { };
+      package = lib.mkPackageOption pkgs "woodpecker-agent" { };
 
       environment = lib.mkOption {
         default = { };
@@ -35,6 +35,16 @@ let
         '';
       };
 
+      path = lib.mkOption {
+        type = lib.types.listOf lib.types.package;
+        default = [ ];
+        example = [ "" ];
+        description = lib.mdDoc ''
+          Additional packages that should be added to the agent's `PATH`.
+          Mostly useful for the `local` backend.
+        '';
+      };
+
       environmentFile = lib.mkOption {
         type = lib.types.listOf lib.types.path;
         default = [ ];
@@ -94,7 +104,7 @@ let
           "-/etc/localtime"
         ];
       };
-      inherit (agentCfg) environment;
+      inherit (agentCfg) environment path;
     };
   };
 in
@@ -106,28 +116,41 @@ in
       agents = lib.mkOption {
         default = { };
         type = lib.types.attrsOf agentModule;
-        example = {
-          docker = {
-            environment = {
-              WOODPECKER_SERVER = "localhost:9000";
-              WOODPECKER_BACKEND = "docker";
-              DOCKER_HOST = "unix:///run/podman/podman.sock";
+        example = lib.literalExpression ''
+          {
+            podman = {
+              environment = {
+                WOODPECKER_SERVER = "localhost:9000";
+                WOODPECKER_BACKEND = "docker";
+                DOCKER_HOST = "unix:///run/podman/podman.sock";
+              };
+
+              extraGroups = [ "podman" ];
+
+              environmentFile = [ "/run/secrets/woodpecker/agent-secret.txt" ];
             };
 
-            extraGroups = [ "docker" ];
+            exec = {
+              environment = {
+                WOODPECKER_SERVER = "localhost:9000";
+                WOODPECKER_BACKEND = "local";
+              };
 
-            environmentFile = "/run/secrets/woodpecker/agent-secret.txt";
-          };
+              environmentFile = [ "/run/secrets/woodpecker/agent-secret.txt" ];
 
-          exec = {
-            environment = {
-              WOODPECKER_SERVER = "localhost:9000";
-              WOODPECKER_BACKEND = "exec";
+              path = [
+                # Needed to clone repos
+                git
+                git-lfs
+                woodpecker-plugin-git
+                # Used by the runner as the default shell
+                bash
+                # Most likely to be used in pipeline definitions
+                coreutils
+              ];
             };
-
-            environmentFile = "/run/secrets/woodpecker/agent-secret.txt";
-          };
-        };
+          }
+        '';
         description = lib.mdDoc "woodpecker-agents configurations";
       };
     };
diff --git a/nixos/modules/services/continuous-integration/woodpecker/server.nix b/nixos/modules/services/continuous-integration/woodpecker/server.nix
index cae5ed7cf1161..4a0f15756c307 100644
--- a/nixos/modules/services/continuous-integration/woodpecker/server.nix
+++ b/nixos/modules/services/continuous-integration/woodpecker/server.nix
@@ -14,7 +14,7 @@ in
   options = {
     services.woodpecker-server = {
       enable = lib.mkEnableOption (lib.mdDoc "the Woodpecker-Server, a CI/CD application for automatic builds, deployments and tests");
-      package = lib.mkPackageOptionMD pkgs "woodpecker-server" { };
+      package = lib.mkPackageOption pkgs "woodpecker-server" { };
       environment = lib.mkOption {
         default = { };
         type = lib.types.attrsOf lib.types.str;
@@ -31,9 +31,9 @@ in
         description = lib.mdDoc "woodpecker-server config environment variables, for other options read the [documentation](https://woodpecker-ci.org/docs/administration/server-config)";
       };
       environmentFile = lib.mkOption {
-        type = lib.types.nullOr lib.types.path;
-        default = null;
-        example = "/root/woodpecker-server.env";
+        type = with lib.types; coercedTo path (f: [ f ]) (listOf path);
+        default = [ ];
+        example = [ "/root/woodpecker-server.env" ];
         description = lib.mdDoc ''
           File to load environment variables
           from. This is helpful for specifying secrets.
@@ -61,7 +61,7 @@ in
           StateDirectoryMode = "0700";
           UMask = "0007";
           ConfigurationDirectory = "woodpecker-server";
-          EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
+          EnvironmentFile = cfg.environmentFile;
           ExecStart = "${cfg.package}/bin/woodpecker-server";
           Restart = "on-failure";
           RestartSec = 15;
diff --git a/nixos/modules/services/databases/aerospike.nix b/nixos/modules/services/databases/aerospike.nix
index 21df4cd0577b1..4923c0f00ddb4 100644
--- a/nixos/modules/services/databases/aerospike.nix
+++ b/nixos/modules/services/databases/aerospike.nix
@@ -41,12 +41,7 @@ in
     services.aerospike = {
       enable = mkEnableOption (lib.mdDoc "Aerospike server");
 
-      package = mkOption {
-        default = pkgs.aerospike;
-        defaultText = literalExpression "pkgs.aerospike";
-        type = types.package;
-        description = lib.mdDoc "Which Aerospike derivation to use";
-      };
+      package = mkPackageOption pkgs "aerospike" { };
 
       workDir = mkOption {
         type = types.str;
@@ -113,6 +108,11 @@ in
     };
     users.groups.aerospike.gid = config.ids.gids.aerospike;
 
+    boot.kernel.sysctl = {
+      "net.core.rmem_max" = mkDefault 15728640;
+      "net.core.wmem_max" = mkDefault 5242880;
+    };
+
     systemd.services.aerospike = rec {
       description = "Aerospike server";
 
@@ -136,14 +136,6 @@ in
           echo "kernel.shmmax too low, setting to 1GB"
           ${pkgs.procps}/bin/sysctl -w kernel.shmmax=1073741824
         fi
-        if [ $(echo "$(cat /proc/sys/net/core/rmem_max) < 15728640" | ${pkgs.bc}/bin/bc) == "1" ]; then
-          echo "increasing socket buffer limit (/proc/sys/net/core/rmem_max): $(cat /proc/sys/net/core/rmem_max) -> 15728640"
-          echo 15728640 > /proc/sys/net/core/rmem_max
-        fi
-        if [ $(echo "$(cat /proc/sys/net/core/wmem_max) <  5242880" | ${pkgs.bc}/bin/bc) == "1"  ]; then
-          echo "increasing socket buffer limit (/proc/sys/net/core/wmem_max): $(cat /proc/sys/net/core/wmem_max) -> 5242880"
-          echo  5242880 > /proc/sys/net/core/wmem_max
-        fi
         install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}"
         install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/smd"
         install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/udf"
diff --git a/nixos/modules/services/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index e26acb88d8c85..adf7213dd13f5 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -11,6 +11,7 @@ let
     recursiveUpdate
     mdDoc
     mkEnableOption
+    mkPackageOption
     mkIf
     mkOption
     types
@@ -122,7 +123,7 @@ in
   options.services.cassandra = {
 
     enable = mkEnableOption (lib.mdDoc ''
-      Apache Cassandra – Scalable and highly available database.
+      Apache Cassandra – Scalable and highly available database
     '');
 
     clusterName = mkOption {
@@ -155,14 +156,8 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.cassandra;
-      defaultText = literalExpression "pkgs.cassandra";
-      example = literalExpression "pkgs.cassandra_3_11";
-      description = mdDoc ''
-        The Apache Cassandra package to use.
-      '';
+    package = mkPackageOption pkgs "cassandra" {
+      example = "cassandra_3_11";
     };
 
     jvmOpts = mkOption {
diff --git a/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix
index dca352ef72fe6..288046677721a 100644
--- a/nixos/modules/services/databases/clickhouse.nix
+++ b/nixos/modules/services/databases/clickhouse.nix
@@ -13,14 +13,7 @@ with lib;
 
       enable = mkEnableOption (lib.mdDoc "ClickHouse database server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.clickhouse;
-        defaultText = lib.literalExpression "pkgs.clickhouse";
-        description = lib.mdDoc ''
-          ClickHouse package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "clickhouse" { };
 
     };
 
diff --git a/nixos/modules/services/databases/cockroachdb.nix b/nixos/modules/services/databases/cockroachdb.nix
index ff77d30588fef..789f086158db0 100644
--- a/nixos/modules/services/databases/cockroachdb.nix
+++ b/nixos/modules/services/databases/cockroachdb.nix
@@ -145,13 +145,8 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.cockroachdb;
-        defaultText = literalExpression "pkgs.cockroachdb";
-        description = lib.mdDoc ''
-          The CockroachDB derivation to use for running the service.
-
+      package = mkPackageOption pkgs "cockroachdb" {
+        extraDescription = ''
           This would primarily be useful to enable Enterprise Edition features
           in your own custom CockroachDB build (Nixpkgs CockroachDB binaries
           only contain open source features and open source code).
diff --git a/nixos/modules/services/databases/couchdb.nix b/nixos/modules/services/databases/couchdb.nix
index 0a81a8dceeeed..72212c390413d 100644
--- a/nixos/modules/services/databases/couchdb.nix
+++ b/nixos/modules/services/databases/couchdb.nix
@@ -36,14 +36,7 @@ in {
 
       enable = mkEnableOption (lib.mdDoc "CouchDB Server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.couchdb3;
-        defaultText = literalExpression "pkgs.couchdb3";
-        description = lib.mdDoc ''
-          CouchDB package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "couchdb3" { };
 
       adminUser = mkOption {
         type = types.str;
@@ -79,7 +72,7 @@ in {
         '';
       };
 
-      # couchdb options: http://docs.couchdb.org/en/latest/config/index.html
+      # couchdb options: https://docs.couchdb.org/en/latest/config/index.html
 
       databaseDir = mkOption {
         type = types.path;
diff --git a/nixos/modules/services/databases/dgraph.nix b/nixos/modules/services/databases/dgraph.nix
index 7f005a9971a69..479754a6447d3 100644
--- a/nixos/modules/services/databases/dgraph.nix
+++ b/nixos/modules/services/databases/dgraph.nix
@@ -55,7 +55,7 @@ in
     services.dgraph = {
       enable = mkEnableOption (lib.mdDoc "Dgraph native GraphQL database with a graph backend");
 
-      package = lib.mkPackageOptionMD pkgs "dgraph" { };
+      package = lib.mkPackageOption pkgs "dgraph" { };
 
       settings = mkOption {
         type = settingsFormat.type;
diff --git a/nixos/modules/services/databases/ferretdb.nix b/nixos/modules/services/databases/ferretdb.nix
new file mode 100644
index 0000000000000..ab55e22bf2146
--- /dev/null
+++ b/nixos/modules/services/databases/ferretdb.nix
@@ -0,0 +1,79 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ferretdb;
+in
+{
+
+  meta.maintainers = with lib.maintainers; [ julienmalka camillemndn ];
+
+  options = {
+    services.ferretdb = {
+      enable = mkEnableOption "FerretDB, an Open Source MongoDB alternative";
+
+      package = mkOption {
+        type = types.package;
+        example = literalExpression "pkgs.ferretdb";
+        default = pkgs.ferretdb;
+        defaultText = "pkgs.ferretdb";
+        description = "FerretDB package to use.";
+      };
+
+      settings = lib.mkOption {
+        type =
+          lib.types.submodule { freeformType = with lib.types; attrsOf str; };
+        example = {
+          FERRETDB_LOG_LEVEL = "warn";
+          FERRETDB_MODE = "normal";
+        };
+        description = ''
+          Additional configuration for FerretDB, see
+          <https://docs.ferretdb.io/configuration/flags/>
+          for supported values.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable
+    {
+
+      services.ferretdb.settings = {
+        FERRETDB_HANDLER = lib.mkDefault "sqlite";
+        FERRETDB_SQLITE_URL = lib.mkDefault "file:/var/lib/ferretdb/";
+      };
+
+      systemd.services.ferretdb = {
+        description = "FerretDB";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        environment = cfg.settings;
+        serviceConfig = {
+          Type = "simple";
+          StateDirectory = "ferretdb";
+          WorkingDirectory = "/var/lib/ferretdb";
+          ExecStart = "${cfg.package}/bin/ferretdb";
+          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;
+          DynamicUser = true;
+        };
+      };
+    };
+}
+
diff --git a/nixos/modules/services/databases/firebird.nix b/nixos/modules/services/databases/firebird.nix
index 26ed46f0e60c0..431233ce5ed41 100644
--- a/nixos/modules/services/databases/firebird.nix
+++ b/nixos/modules/services/databases/firebird.nix
@@ -17,7 +17,7 @@
 # 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
+# https://www.firebirdsql.org/manual/qsg25-classic-or-super.html
 
 with lib;
 
@@ -42,13 +42,9 @@ in
 
       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 = lib.mdDoc ''
-          Which Firebird package to be installed: `pkgs.firebird_3`
+      package = mkPackageOption pkgs "firebird" {
+        example = "firebird_3";
+        extraDescription = ''
           For SuperServer use override: `pkgs.firebird_3.override { superServer = true; };`
         '';
       };
@@ -147,7 +143,7 @@ in
       # ConnectionTimeout = 180
 
       #RemoteServiceName = gds_db
-      RemoteServicePort = ${cfg.port}
+      RemoteServicePort = ${toString cfg.port}
 
       # randomly choose port for server Event Notification
       #RemoteAuxPort = 0
diff --git a/nixos/modules/services/databases/hbase-standalone.nix b/nixos/modules/services/databases/hbase-standalone.nix
index 1ee73ec8d1ff2..08ae7625d50ab 100644
--- a/nixos/modules/services/databases/hbase-standalone.nix
+++ b/nixos/modules/services/databases/hbase-standalone.nix
@@ -46,15 +46,7 @@ in {
         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 = lib.mdDoc ''
-          HBase package to use.
-        '';
-      };
-
+      package = mkPackageOption pkgs "hbase" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/databases/influxdb.nix b/nixos/modules/services/databases/influxdb.nix
index b3361d2014caf..adb212ab08d0d 100644
--- a/nixos/modules/services/databases/influxdb.nix
+++ b/nixos/modules/services/databases/influxdb.nix
@@ -116,12 +116,7 @@ in
         type = types.bool;
       };
 
-      package = mkOption {
-        default = pkgs.influxdb;
-        defaultText = literalExpression "pkgs.influxdb";
-        description = lib.mdDoc "Which influxdb derivation to use";
-        type = types.package;
-      };
+      package = mkPackageOption pkgs "influxdb" { };
 
       user = mkOption {
         default = "influxdb";
@@ -166,6 +161,7 @@ in
         ExecStart = ''${cfg.package}/bin/influxd -config "${configFile}"'';
         User = cfg.user;
         Group = cfg.group;
+        Restart = "on-failure";
       };
       postStart =
         let
diff --git a/nixos/modules/services/databases/influxdb2.nix b/nixos/modules/services/databases/influxdb2.nix
index 3740cd01b5dc0..2a67d87d4bbb8 100644
--- a/nixos/modules/services/databases/influxdb2.nix
+++ b/nixos/modules/services/databases/influxdb2.nix
@@ -19,6 +19,7 @@ let
     mapAttrsToList
     mdDoc
     mkEnableOption
+    mkPackageOption
     mkIf
     mkOption
     nameValuePair
@@ -278,12 +279,7 @@ in
     services.influxdb2 = {
       enable = mkEnableOption (mdDoc "the influxdb2 server");
 
-      package = mkOption {
-        default = pkgs.influxdb2-server;
-        defaultText = literalExpression "pkgs.influxdb2";
-        description = mdDoc "influxdb2 derivation to use.";
-        type = types.package;
-      };
+      package = mkPackageOption pkgs "influxdb2" { };
 
       settings = mkOption {
         default = { };
diff --git a/nixos/modules/services/databases/lldap.nix b/nixos/modules/services/databases/lldap.nix
index 960792d0805f1..e821da8e58aa3 100644
--- a/nixos/modules/services/databases/lldap.nix
+++ b/nixos/modules/services/databases/lldap.nix
@@ -8,7 +8,7 @@ in
   options.services.lldap = with lib; {
     enable = mkEnableOption (mdDoc "lldap");
 
-    package = mkPackageOptionMD pkgs "lldap" { };
+    package = mkPackageOption pkgs "lldap" { };
 
     environment = mkOption {
       type = with types; attrsOf str;
@@ -104,6 +104,7 @@ in
   config = lib.mkIf cfg.enable {
     systemd.services.lldap = {
       description = "Lightweight LDAP server (lldap)";
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
diff --git a/nixos/modules/services/databases/monetdb.nix b/nixos/modules/services/databases/monetdb.nix
index 5573b530a9131..1dddeda0959c0 100644
--- a/nixos/modules/services/databases/monetdb.nix
+++ b/nixos/modules/services/databases/monetdb.nix
@@ -14,12 +14,7 @@ in {
 
       enable = mkEnableOption (lib.mdDoc "the MonetDB database server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.monetdb;
-        defaultText = literalExpression "pkgs.monetdb";
-        description = lib.mdDoc "MonetDB package to use.";
-      };
+      package = mkPackageOption pkgs "monetdb" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/databases/mongodb.nix b/nixos/modules/services/databases/mongodb.nix
index 8f3be1492e9e1..f10364bc76c10 100644
--- a/nixos/modules/services/databases/mongodb.nix
+++ b/nixos/modules/services/databases/mongodb.nix
@@ -31,14 +31,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "the MongoDB server");
 
-      package = mkOption {
-        default = pkgs.mongodb;
-        defaultText = literalExpression "pkgs.mongodb";
-        type = types.package;
-        description = lib.mdDoc ''
-          Which MongoDB derivation to use.
-        '';
-      };
+      package = mkPackageOption pkgs "mongodb" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/databases/neo4j.nix b/nixos/modules/services/databases/neo4j.nix
index 0905024240286..56b916ee3758c 100644
--- a/nixos/modules/services/databases/neo4j.nix
+++ b/nixos/modules/services/databases/neo4j.nix
@@ -174,14 +174,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.neo4j;
-      defaultText = literalExpression "pkgs.neo4j";
-      description = lib.mdDoc ''
-        Neo4j package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "neo4j" { };
 
     readOnly = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index cba3442023cb8..df36e37976a44 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -91,13 +91,8 @@ in {
         description = lib.mdDoc "Whether to enable the ldap server.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.openldap;
-        defaultText = literalExpression "pkgs.openldap";
-        description = lib.mdDoc ''
-          OpenLDAP package to use.
-
+      package = mkPackageOption pkgs "openldap" {
+        extraDescription = ''
           This can be used to, for example, set an OpenLDAP package
           with custom overrides to enable modules or other
           functionality.
@@ -299,6 +294,7 @@ in {
         "man:slapd-mdb"
       ];
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       serviceConfig = {
         User = cfg.user;
diff --git a/nixos/modules/services/databases/opentsdb.nix b/nixos/modules/services/databases/opentsdb.nix
index 288b716fce03f..25f413db809f3 100644
--- a/nixos/modules/services/databases/opentsdb.nix
+++ b/nixos/modules/services/databases/opentsdb.nix
@@ -17,14 +17,7 @@ in {
 
       enable = mkEnableOption (lib.mdDoc "OpenTSDB");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.opentsdb;
-        defaultText = literalExpression "pkgs.opentsdb";
-        description = lib.mdDoc ''
-          OpenTSDB package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "opentsdb" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/databases/pgbouncer.nix b/nixos/modules/services/databases/pgbouncer.nix
index 1aec03c114d1a..65b287e84442b 100644
--- a/nixos/modules/services/databases/pgbouncer.nix
+++ b/nixos/modules/services/databases/pgbouncer.nix
@@ -82,14 +82,7 @@ in {
 
     enable = mkEnableOption (lib.mdDoc "PostgreSQL connection pooler");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.pgbouncer;
-      defaultText = literalExpression "pkgs.pgbouncer";
-      description = lib.mdDoc ''
-        The pgbouncer package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "pgbouncer" { };
 
     openFirewall = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/databases/pgmanage.nix b/nixos/modules/services/databases/pgmanage.nix
index cbf988d596f46..4b963aee46405 100644
--- a/nixos/modules/services/databases/pgmanage.nix
+++ b/nixos/modules/services/databases/pgmanage.nix
@@ -46,14 +46,7 @@ in {
   options.services.pgmanage = {
     enable = mkEnableOption (lib.mdDoc "PostgreSQL Administration for the web");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.pgmanage;
-      defaultText = literalExpression "pkgs.pgmanage";
-      description = lib.mdDoc ''
-        The pgmanage package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "pgmanage" { };
 
     connections = mkOption {
       type = types.attrsOf types.str;
@@ -66,7 +59,7 @@ in {
         pgmanage requires at least one PostgreSQL server be defined.
 
         Detailed information about PostgreSQL connection strings is available at:
-        <http://www.postgresql.org/docs/current/static/libpq-connect.html>
+        <https://www.postgresql.org/docs/current/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
@@ -187,7 +180,7 @@ in {
       serviceConfig = {
         User         = pgmanage;
         Group        = pgmanage;
-        ExecStart    = "${pkgs.pgmanage}/sbin/pgmanage -c ${confFile}" +
+        ExecStart    = "${cfg.package}/sbin/pgmanage -c ${confFile}" +
                        optionalString cfg.localOnly " --local-only=true";
       };
     };
diff --git a/nixos/modules/services/databases/postgresql.md b/nixos/modules/services/databases/postgresql.md
index 4d66ee38be426..7d141f12b5dea 100644
--- a/nixos/modules/services/databases/postgresql.md
+++ b/nixos/modules/services/databases/postgresql.md
@@ -5,7 +5,7 @@
 
 *Source:* {file}`modules/services/databases/postgresql.nix`
 
-*Upstream documentation:* <http://www.postgresql.org/docs/>
+*Upstream documentation:* <https://www.postgresql.org/docs/>
 
 <!-- FIXME: more stuff, like maintainer? -->
 
@@ -17,9 +17,9 @@ PostgreSQL is an advanced, free relational database.
 To enable PostgreSQL, add the following to your {file}`configuration.nix`:
 ```
 services.postgresql.enable = true;
-services.postgresql.package = pkgs.postgresql_11;
+services.postgresql.package = pkgs.postgresql_15;
 ```
-Note that you are required to specify the desired version of PostgreSQL (e.g. `pkgs.postgresql_11`). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for [](#opt-services.postgresql.package) such as the most recent release of PostgreSQL.
+Note that you are required to specify the desired version of PostgreSQL (e.g. `pkgs.postgresql_15`). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for [](#opt-services.postgresql.package) such as the most recent release of PostgreSQL.
 
 <!--
 After running {command}`nixos-rebuild`, you can verify
@@ -39,6 +39,125 @@ By default, PostgreSQL stores its databases in {file}`/var/lib/postgresql/$psqlS
 services.postgresql.dataDir = "/data/postgresql";
 ```
 
+## Initializing {#module-services-postgres-initializing}
+
+As of NixOS 23.11,
+`services.postgresql.ensureUsers.*.ensurePermissions` has been
+deprecated, after a change to default permissions in PostgreSQL 15
+invalidated most of its previous use cases:
+
+- In psql < 15, `ALL PRIVILEGES` used to include `CREATE TABLE`, where
+  in psql >= 15 that would be a separate permission
+- psql >= 15 instead gives only the database owner create permissions
+- Even on psql < 15 (or databases migrated to >= 15), it is
+  recommended to manually assign permissions along these lines
+  - https://www.postgresql.org/docs/release/15.0/
+  - https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PRIV
+
+### Assigning ownership {#module-services-postgres-initializing-ownership}
+
+Usually, the database owner should be a database user of the same
+name. This can be done with
+`services.postgresql.ensureUsers.*.ensureDBOwnership = true;`.
+
+If the database user name equals the connecting system user name,
+postgres by default will accept a passwordless connection via unix
+domain socket. This makes it possible to run many postgres-backed
+services without creating any database secrets at all
+
+### Assigning extra permissions {#module-services-postgres-initializing-extra-permissions}
+
+For many cases, it will be enough to have the database user be the
+owner. Until `services.postgresql.ensureUsers.*.ensurePermissions` has
+been re-thought, if more users need access to the database, please use
+one of the following approaches:
+
+**WARNING:** `services.postgresql.initialScript` is not recommended
+for `ensurePermissions` replacement, as that is *only run on first
+start of PostgreSQL*.
+
+**NOTE:** all of these methods may be obsoleted, when `ensure*` is
+reworked, but it is expected that they will stay viable for running
+database migrations.
+
+**NOTE:** please make sure that any added migrations are idempotent (re-runnable).
+
+#### as superuser {#module-services-postgres-initializing-extra-permissions-superuser}
+
+**Advantage:** compatible with postgres < 15, because it's run
+as the database superuser `postgres`.
+
+##### in database `postStart` {#module-services-postgres-initializing-extra-permissions-superuser-post-start}
+
+**Disadvantage:** need to take care of ordering yourself. In this
+example, `mkAfter` ensures that permissions are assigned after any
+databases from `ensureDatabases` and `extraUser1` from `ensureUsers`
+are already created.
+
+```nix
+    systemd.services.postgresql.postStart = lib.mkAfter ''
+      $PSQL service1 -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+      $PSQL service1 -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+      # ....
+    '';
+```
+
+##### in intermediate oneshot service {#module-services-postgres-initializing-extra-permissions-superuser-oneshot}
+
+```nix
+    systemd.services."migrate-service1-db1" = {
+      serviceConfig.Type = "oneshot";
+      requiredBy = "service1.service";
+      before = "service1.service";
+      after = "postgresql.service";
+      serviceConfig.User = "postgres";
+      environment.PSQL = "psql --port=${toString services.postgresql.port}";
+      path = [ postgresql ];
+      script = ''
+        $PSQL service1 -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+        $PSQL service1 -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+        # ....
+      '';
+    };
+```
+
+#### as service user {#module-services-postgres-initializing-extra-permissions-service-user}
+
+**Advantage:** re-uses systemd's dependency ordering;
+
+**Disadvantage:** relies on service user having grant permission. To be combined with `ensureDBOwnership`.
+
+##### in service `preStart` {#module-services-postgres-initializing-extra-permissions-service-user-pre-start}
+
+```nix
+    environment.PSQL = "psql --port=${toString services.postgresql.port}";
+    path = [ postgresql ];
+    systemd.services."service1".preStart = ''
+      $PSQL -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+      $PSQL -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+      # ....
+    '';
+```
+
+##### in intermediate oneshot service {#module-services-postgres-initializing-extra-permissions-service-user-oneshot}
+
+```nix
+    systemd.services."migrate-service1-db1" = {
+      serviceConfig.Type = "oneshot";
+      requiredBy = "service1.service";
+      before = "service1.service";
+      after = "postgresql.service";
+      serviceConfig.User = "service1";
+      environment.PSQL = "psql --port=${toString services.postgresql.port}";
+      path = [ postgresql ];
+      script = ''
+        $PSQL -c 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO "extraUser1"'
+        $PSQL -c 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "extraUser1"'
+        # ....
+      '';
+    };
+```
+
 ## Upgrading {#module-services-postgres-upgrading}
 
 ::: {.note}
@@ -119,27 +238,27 @@ A complete list of options for the PostgreSQL module may be found [here](#opt-se
 
 ## Plugins {#module-services-postgres-plugins}
 
-Plugins collection for each PostgreSQL version can be accessed with `.pkgs`. For example, for `pkgs.postgresql_11` package, its plugin collection is accessed by `pkgs.postgresql_11.pkgs`:
+Plugins collection for each PostgreSQL version can be accessed with `.pkgs`. For example, for `pkgs.postgresql_15` package, its plugin collection is accessed by `pkgs.postgresql_15.pkgs`:
 ```ShellSession
 $ nix repl '<nixpkgs>'
 
 Loading '<nixpkgs>'...
 Added 10574 variables.
 
-nix-repl> postgresql_11.pkgs.<TAB><TAB>
-postgresql_11.pkgs.cstore_fdw        postgresql_11.pkgs.pg_repack
-postgresql_11.pkgs.pg_auto_failover  postgresql_11.pkgs.pg_safeupdate
-postgresql_11.pkgs.pg_bigm           postgresql_11.pkgs.pg_similarity
-postgresql_11.pkgs.pg_cron           postgresql_11.pkgs.pg_topn
-postgresql_11.pkgs.pg_hll            postgresql_11.pkgs.pgjwt
-postgresql_11.pkgs.pg_partman        postgresql_11.pkgs.pgroonga
+nix-repl> postgresql_15.pkgs.<TAB><TAB>
+postgresql_15.pkgs.cstore_fdw        postgresql_15.pkgs.pg_repack
+postgresql_15.pkgs.pg_auto_failover  postgresql_15.pkgs.pg_safeupdate
+postgresql_15.pkgs.pg_bigm           postgresql_15.pkgs.pg_similarity
+postgresql_15.pkgs.pg_cron           postgresql_15.pkgs.pg_topn
+postgresql_15.pkgs.pg_hll            postgresql_15.pkgs.pgjwt
+postgresql_15.pkgs.pg_partman        postgresql_15.pkgs.pgroonga
 ...
 ```
 
 To add plugins via NixOS configuration, set `services.postgresql.extraPlugins`:
 ```
-services.postgresql.package = pkgs.postgresql_11;
-services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [
+services.postgresql.package = pkgs.postgresql_12;
+services.postgresql.extraPlugins = ps: with ps; [
   pg_repack
   postgis
 ];
@@ -148,7 +267,7 @@ services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [
 You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using function `.withPackages`. For example, creating a custom PostgreSQL package in an overlay can look like:
 ```
 self: super: {
-  postgresql_custom = self.postgresql_11.withPackages (ps: [
+  postgresql_custom = self.postgresql_12.withPackages (ps: [
     ps.pg_repack
     ps.postgis
   ]);
@@ -158,9 +277,9 @@ self: super: {
 Here's a recipe on how to override a particular plugin through an overlay:
 ```
 self: super: {
-  postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // {
-    pkgs = super.postgresql_11.pkgs // {
-      pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: {
+  postgresql_15 = super.postgresql_15.override { this = self.postgresql_15; } // {
+    pkgs = super.postgresql_15.pkgs // {
+      pg_repack = super.postgresql_15.pkgs.pg_repack.overrideAttrs (_: {
         name = "pg_repack-v20181024";
         src = self.fetchzip {
           url = "https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz";
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 0acaf0fd00a67..ed5915735730b 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -18,7 +18,7 @@ let
     in
     if cfg.extraPlugins == []
       then base
-      else base.withPackages (_: cfg.extraPlugins);
+      else base.withPackages cfg.extraPlugins;
 
   toStr = value:
     if true == value then "yes"
@@ -53,12 +53,8 @@ in
 
       enableJIT = mkEnableOption (lib.mdDoc "JIT support");
 
-      package = mkOption {
-        type = types.package;
-        example = literalExpression "pkgs.postgresql_11";
-        description = lib.mdDoc ''
-          PostgreSQL package to use.
-        '';
+      package = mkPackageOption pkgs "postgresql" {
+        example = "postgresql_15";
       };
 
       port = mkOption {
@@ -78,7 +74,7 @@ in
       dataDir = mkOption {
         type = types.path;
         defaultText = literalExpression ''"/var/lib/postgresql/''${config.services.postgresql.package.psqlSchema}"'';
-        example = "/var/lib/postgresql/11";
+        example = "/var/lib/postgresql/15";
         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
@@ -106,12 +102,14 @@ in
       identMap = mkOption {
         type = types.lines;
         default = "";
+        example = ''
+          map-name-0 system-username-0 database-username-0
+          map-name-1 system-username-1 database-username-1
+        '';
         description = lib.mdDoc ''
           Defines the mapping from system users to database users.
 
-          The general form is:
-
-          map-name system-username database-username
+          See the [auth doc](https://postgresql.org/docs/current/auth-username-maps.html).
         '';
       };
 
@@ -128,6 +126,11 @@ in
       initialScript = mkOption {
         type = types.nullOr types.path;
         default = null;
+        example = literalExpression ''
+          pkgs.writeText "init-sql-script" '''
+            alter user postgres with password 'myPassword';
+          ''';'';
+
         description = lib.mdDoc ''
           A file containing SQL statements to execute on first startup.
         '';
@@ -161,7 +164,12 @@ in
             ensurePermissions = mkOption {
               type = types.attrsOf types.str;
               default = {};
+              visible = false; # This option has been deprecated.
               description = lib.mdDoc ''
+                This option is DEPRECATED and should not be used in nixpkgs anymore,
+                use `ensureDBOwnership` instead. It can also break with newer
+                versions of PostgreSQL (≥ 15).
+
                 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
@@ -180,6 +188,16 @@ in
               '';
             };
 
+            ensureDBOwnership = mkOption {
+              type = types.bool;
+              default = false;
+              description = mdDoc ''
+                Grants the user ownership to a database with the same name.
+                This database must be defined manually in
+                [](#opt-services.postgresql.ensureDatabases).
+              '';
+            };
+
             ensureClauses = mkOption {
               description = lib.mdDoc ''
                 An attrset of clauses to grant to the user. Under the hood this uses the
@@ -331,26 +349,21 @@ in
         });
         default = [];
         description = lib.mdDoc ''
-          Ensures that the specified users exist and have at least the ensured permissions.
+          Ensures that the specified users exist.
           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.
-          This option will never delete existing users or remove permissions, especially not when the value of this
-          option is changed. This means that users created and permissions assigned once through this option or
-          otherwise have to be removed manually.
+          This option will never delete existing users or remove DB ownership of databases
+          once granted with `ensureDBOwnership = true;`. This means that this must be
+          cleaned up manually when changing after changing the config in here.
         '';
         example = literalExpression ''
           [
             {
               name = "nextcloud";
-              ensurePermissions = {
-                "DATABASE nextcloud" = "ALL PRIVILEGES";
-              };
             }
             {
               name = "superuser";
-              ensurePermissions = {
-                "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
-              };
+              ensureDBOwnership = true;
             }
           ]
         '';
@@ -378,12 +391,11 @@ in
       };
 
       extraPlugins = mkOption {
-        type = types.listOf types.path;
-        default = [];
-        example = literalExpression "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
+        type = with types; coercedTo (listOf path) (path: _ignorePg: path) (functionTo (listOf path));
+        default = _: [];
+        example = literalExpression "ps: with ps; [ postgis pg_repack ]";
         description = lib.mdDoc ''
-          List of PostgreSQL plugins. PostgreSQL version for each plugin should
-          match version for `services.postgresql.package` value.
+          List of PostgreSQL plugins.
         '';
       };
 
@@ -392,7 +404,7 @@ in
         default = {};
         description = lib.mdDoc ''
           PostgreSQL configuration. Refer to
-          <https://www.postgresql.org/docs/11/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
+          <https://www.postgresql.org/docs/current/config-setting.html#CONFIG-SETTING-CONFIGURATION-FILE>
           for an overview of `postgresql.conf`.
 
           ::: {.note}
@@ -438,6 +450,27 @@ in
 
   config = mkIf cfg.enable {
 
+    assertions = map ({ name, ensureDBOwnership, ... }: {
+      assertion = ensureDBOwnership -> builtins.elem name cfg.ensureDatabases;
+      message = ''
+        For each database user defined with `services.postgresql.ensureUsers` and
+        `ensureDBOwnership = true;`, a database with the same name must be defined
+        in `services.postgresql.ensureDatabases`.
+
+        Offender: ${name} has not been found among databases.
+      '';
+    }) cfg.ensureUsers;
+    # `ensurePermissions` is now deprecated, let's avoid it.
+    warnings = lib.optional (any ({ ensurePermissions, ... }: ensurePermissions != {}) cfg.ensureUsers) "
+      `services.postgresql.ensureUsers.*.ensurePermissions` is used in your expressions,
+      this option is known to be broken with newer PostgreSQL versions,
+      consider migrating to `services.postgresql.ensureUsers.*.ensureDBOwnership` or
+      consult the release notes or manual for more migration guidelines.
+
+      This option will be removed in NixOS 24.05 unless it sees significant
+      maintenance improvements.
+    ";
+
     services.postgresql.settings =
       {
         hba_file = "${pkgs.writeText "pg_hba.conf" cfg.authentication}";
@@ -451,9 +484,10 @@ in
 
     services.postgresql.package = let
         mkThrow = ver: throw "postgresql_${ver} was removed, please upgrade your postgresql version.";
-        base = if versionAtLeast config.system.stateVersion "22.05" then pkgs.postgresql_14
+        base = if versionAtLeast config.system.stateVersion "23.11" then pkgs.postgresql_15
+            else 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 "20.03" then mkThrow "11"
             else if versionAtLeast config.system.stateVersion "17.09" then mkThrow "9_6"
             else mkThrow "9_5";
     in
@@ -464,13 +498,16 @@ in
 
     services.postgresql.dataDir = mkDefault "/var/lib/postgresql/${cfg.package.psqlSchema}";
 
-    services.postgresql.authentication = mkAfter
+    services.postgresql.authentication = mkMerge [
+      (mkBefore "# Generated file; do not edit!")
+      (mkAfter
       ''
-        # Generated file; do not edit!
+        # default value of services.postgresql.authentication
         local all all              peer
         host  all all 127.0.0.1/32 md5
         host  all all ::1/128      md5
-      '';
+      '')
+    ];
 
     users.users.postgres =
       { name = "postgres";
@@ -545,12 +582,15 @@ in
             ${
               concatMapStrings
               (user:
-                let
+              let
                   userPermissions = concatStringsSep "\n"
                     (mapAttrsToList
                       (database: permission: ''$PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"' '')
                       user.ensurePermissions
                     );
+                  dbOwnershipStmt = optionalString
+                    user.ensureDBOwnership
+                    ''$PSQL -tAc 'ALTER DATABASE "${user.name}" OWNER TO "${user.name}";' '';
 
                   filteredClauses = filterAttrs (name: value: value != null) user.ensureClauses;
 
@@ -561,6 +601,8 @@ in
                   $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"'
                   ${userPermissions}
                   ${userClauses}
+
+                  ${dbOwnershipStmt}
                 ''
               )
               cfg.ensureUsers
@@ -577,7 +619,7 @@ in
                    else "simple";
 
             # Shut down Postgres using SIGINT ("Fast Shutdown mode").  See
-            # http://www.postgresql.org/docs/current/static/server-shutdown.html
+            # https://www.postgresql.org/docs/current/server-shutdown.html
             KillSignal = "SIGINT";
             KillMode = "mixed";
 
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index 1464f4487e39d..2e644895a2602 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -54,16 +54,11 @@ in {
   options = {
 
     services.redis = {
-      package = mkOption {
-        type = types.package;
-        default = pkgs.redis;
-        defaultText = literalExpression "pkgs.redis";
-        description = lib.mdDoc "Which Redis derivation to use.";
-      };
+      package = mkPackageOption pkgs "redis" { };
 
       vmOverCommit = mkEnableOption (lib.mdDoc ''
         setting of vm.overcommit_memory to 1
-        (Suggested for Background Saving: http://redis.io/topics/faq)
+        (Suggested for Background Saving: <https://redis.io/docs/get-started/faq/>)
       '');
 
       servers = mkOption {
@@ -75,7 +70,7 @@ in {
               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).
+              e.g. (https://redis.io/topics/latency)
             '');
 
             user = mkOption {
@@ -393,9 +388,7 @@ in {
         ProtectKernelModules = true;
         ProtectKernelTunables = true;
         ProtectControlGroups = true;
-        RestrictAddressFamilies =
-          optionals (conf.port != 0) ["AF_INET" "AF_INET6"] ++
-          optional (conf.unixSocket != null) "AF_UNIX";
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
         RestrictNamespaces = true;
         LockPersonality = true;
         MemoryDenyWriteExecute = true;
diff --git a/nixos/modules/services/databases/surrealdb.nix b/nixos/modules/services/databases/surrealdb.nix
index 050a5336cb4c6..55216d022d1cf 100644
--- a/nixos/modules/services/databases/surrealdb.nix
+++ b/nixos/modules/services/databases/surrealdb.nix
@@ -8,16 +8,9 @@ in {
 
   options = {
     services.surrealdb = {
-      enable = mkEnableOption (lib.mdDoc "A scalable, distributed, collaborative, document-graph database, for the realtime web ");
+      enable = mkEnableOption (lib.mdDoc "SurrealDB, 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.
-        '';
-      };
+      package = mkPackageOption pkgs "surrealdb" { };
 
       dbPath = mkOption {
         type = types.str;
@@ -47,17 +40,13 @@ in {
         example = 8000;
       };
 
-      userNamePath = mkOption {
-        type = types.path;
-        description = lib.mdDoc ''
-          Path to read the username from.
-        '';
-      };
-
-      passwordPath = mkOption {
-        type = types.path;
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--allow-all" "--auth" "--user root" "--pass root" ];
         description = lib.mdDoc ''
-          Path to read the password from.
+          Specify a list of additional command line flags,
+          which get escaped and are then passed to surrealdb.
         '';
       };
     };
@@ -73,19 +62,8 @@ in {
       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}"
-        ];
-
+        ExecStart = "${cfg.package}/bin/surreal start --bind ${cfg.host}:${toString cfg.port} ${escapeShellArgs cfg.extraFlags} -- ${cfg.dbPath}";
         DynamicUser = true;
         Restart = "on-failure";
         StateDirectory = "surrealdb";
diff --git a/nixos/modules/services/databases/victoriametrics.nix b/nixos/modules/services/databases/victoriametrics.nix
index 638066a42dbdb..0ad2028c95b08 100644
--- a/nixos/modules/services/databases/victoriametrics.nix
+++ b/nixos/modules/services/databases/victoriametrics.nix
@@ -3,14 +3,7 @@ let cfg = config.services.victoriametrics; in
 {
   options.services.victoriametrics = with lib; {
     enable = mkEnableOption (lib.mdDoc "victoriametrics");
-    package = mkOption {
-      type = types.package;
-      default = pkgs.victoriametrics;
-      defaultText = literalExpression "pkgs.victoriametrics";
-      description = lib.mdDoc ''
-        The VictoriaMetrics distribution to use.
-      '';
-    };
+    package = mkPackageOption pkgs "victoriametrics" { };
     listenAddress = mkOption {
       default = ":8428";
       type = types.str;
diff --git a/nixos/modules/services/desktops/ayatana-indicators.nix b/nixos/modules/services/desktops/ayatana-indicators.nix
new file mode 100644
index 0000000000000..abc687bbd43dd
--- /dev/null
+++ b/nixos/modules/services/desktops/ayatana-indicators.nix
@@ -0,0 +1,58 @@
+{ config
+, pkgs
+, lib
+, ...
+}:
+
+let
+  cfg = config.services.ayatana-indicators;
+in
+{
+  options.services.ayatana-indicators = {
+    enable = lib.mkEnableOption (lib.mdDoc ''
+      Ayatana Indicators, a continuation of Canonical's Application Indicators
+    '');
+
+    packages = lib.mkOption {
+      type = lib.types.listOf lib.types.package;
+      default = [ ];
+      example = lib.literalExpression "with pkgs; [ ayatana-indicator-messages ]";
+      description = lib.mdDoc ''
+        List of packages containing Ayatana Indicator services
+        that should be brought up by the SystemD "ayatana-indicators" user target.
+
+        Packages specified here must have passthru.ayatana-indicators set correctly.
+
+        If, how, and where these indicators are displayed will depend on your DE.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      systemPackages = cfg.packages;
+
+      pathsToLink = [
+        "/share/ayatana"
+      ];
+    };
+
+    # libayatana-common's ayatana-indicators.target with explicit Wants & Before to bring up requested indicator services
+    systemd.user.targets."ayatana-indicators" =
+      let
+        indicatorServices = lib.lists.flatten
+          (map
+            (pkg:
+              (map (ind: "${ind}.service") pkg.passthru.ayatana-indicators))
+            cfg.packages);
+      in
+      {
+        description = "Target representing the lifecycle of the Ayatana Indicators. Each indicator should be bound to it in its individual service file";
+        partOf = [ "graphical-session.target" ];
+        wants = indicatorServices;
+        before = indicatorServices;
+      };
+  };
+
+  meta.maintainers = with lib.maintainers; [ OPNA2608 ];
+}
diff --git a/nixos/modules/services/desktops/deepin/app-services.nix b/nixos/modules/services/desktops/deepin/app-services.nix
index 6f9932e487336..a6c33af03e959 100644
--- a/nixos/modules/services/desktops/deepin/app-services.nix
+++ b/nixos/modules/services/desktops/deepin/app-services.nix
@@ -14,7 +14,7 @@ with lib;
 
     services.deepin.app-services = {
 
-      enable = mkEnableOption (lib.mdDoc "Service collection of DDE applications, including dconfig-center");
+      enable = mkEnableOption (lib.mdDoc "service collection of DDE applications, including dconfig-center");
 
     };
 
@@ -25,8 +25,17 @@ with lib;
 
   config = mkIf config.services.deepin.app-services.enable {
 
-    environment.systemPackages = [ pkgs.deepin.dde-app-services ];
+    users.groups.dde-dconfig-daemon = { };
+    users.users.dde-dconfig-daemon = {
+      description = "Dconfig daemon user";
+      home = "/var/lib/dde-dconfig-daemon";
+      createHome = true;
+      group = "dde-dconfig-daemon";
+      isSystemUser = true;
+    };
 
+    environment.systemPackages = [ pkgs.deepin.dde-app-services ];
+    systemd.packages = [ pkgs.deepin.dde-app-services ];
     services.dbus.packages = [ pkgs.deepin.dde-app-services ];
 
     environment.pathsToLink = [ "/share/dsg" ];
diff --git a/nixos/modules/services/desktops/deepin/dde-api.nix b/nixos/modules/services/desktops/deepin/dde-api.nix
index 472d9860c1089..459876febf21f 100644
--- a/nixos/modules/services/desktops/deepin/dde-api.nix
+++ b/nixos/modules/services/desktops/deepin/dde-api.nix
@@ -15,8 +15,8 @@ with lib;
     services.deepin.dde-api = {
 
       enable = mkEnableOption (lib.mdDoc ''
-        Provides some dbus interfaces that is used for screen zone detecting,
-        thumbnail generating, and sound playing in Deepin Desktop Environment.
+        some dbus interfaces that is used for screen zone detecting,
+        thumbnail generating, and sound playing in Deepin Desktop Environment
       '');
 
     };
diff --git a/nixos/modules/services/desktops/deepin/dde-daemon.nix b/nixos/modules/services/desktops/deepin/dde-daemon.nix
index 9377f523ebf9c..356d323bcbdf9 100644
--- a/nixos/modules/services/desktops/deepin/dde-daemon.nix
+++ b/nixos/modules/services/desktops/deepin/dde-daemon.nix
@@ -14,7 +14,7 @@ with lib;
 
     services.deepin.dde-daemon = {
 
-      enable = mkEnableOption (lib.mdDoc "Daemon for handling the deepin session settings");
+      enable = mkEnableOption (lib.mdDoc "daemon for handling the deepin session settings");
 
     };
 
diff --git a/nixos/modules/services/desktops/flatpak.md b/nixos/modules/services/desktops/flatpak.md
index 65b1554d79b4e..af71d85b5a157 100644
--- a/nixos/modules/services/desktops/flatpak.md
+++ b/nixos/modules/services/desktops/flatpak.md
@@ -18,6 +18,7 @@ in other cases, you will need to add something like the following to your
 {file}`configuration.nix`:
 ```
   xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+  xdg.portal.config.common.default = "gtk";
 ```
 
 Then, you will need to add a repository, for example,
diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index d99faf381e019..4c26e6874023a 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -35,6 +35,7 @@ in {
     services.dbus.packages = [ pkgs.flatpak ];
 
     systemd.packages = [ pkgs.flatpak ];
+    systemd.tmpfiles.packages = [ pkgs.flatpak ];
 
     environment.profiles = [
       "$HOME/.local/share/flatpak/exports"
diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix
index b04f46c26a568..2a68bb0b55f3a 100644
--- a/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixos/modules/services/desktops/geoclue2.nix
@@ -200,6 +200,7 @@ in
     };
 
     systemd.services.geoclue = {
+      wants = lib.optionals cfg.enableWifi [ "network-online.target" ];
       after = lib.optionals cfg.enableWifi [ "network-online.target" ];
       # restart geoclue service when the configuration changes
       restartTriggers = [
@@ -217,6 +218,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" ];
+        wants = lib.optionals cfg.enableWifi [ "network-online.target" ];
         after = lib.optionals cfg.enableWifi [ "network-online.target" ];
         unitConfig.ConditionUser = "!@system";
         serviceConfig = {
diff --git a/nixos/modules/services/desktops/gnome/at-spi2-core.nix b/nixos/modules/services/desktops/gnome/at-spi2-core.nix
index 10a2f1f9eca0d..d0320c1e6307a 100644
--- a/nixos/modules/services/desktops/gnome/at-spi2-core.nix
+++ b/nixos/modules/services/desktops/gnome/at-spi2-core.nix
@@ -51,7 +51,7 @@ with lib;
     })
 
     (mkIf (!config.services.gnome.at-spi2-core.enable) {
-      environment.variables = {
+      environment.sessionVariables = {
         NO_AT_BRIDGE = "1";
         GTK_A11Y = "none";
       };
diff --git a/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix b/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
index 5d4ddce94220f..4f680eabbe15c 100644
--- a/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
+++ b/nixos/modules/services/desktops/gnome/gnome-browser-connector.nix
@@ -24,8 +24,8 @@ in
 
   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.
+      native host connector for the GNOME Shell browser extension, a DBus service
+      allowing to install GNOME Shell extensions from a web browser
     '');
   };
 
@@ -42,6 +42,6 @@ in
 
     services.dbus.packages = [ pkgs.gnome-browser-connector ];
 
-    nixpkgs.config.firefox.enableGnomeExtensions = true;
+    programs.firefox.nativeMessagingHosts.packages = [ pkgs.gnome-browser-connector ];
   };
 }
diff --git a/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix b/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
index f24e6f1eb1550..6eaf861e49743 100644
--- a/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
+++ b/nixos/modules/services/desktops/gnome/gnome-initial-setup.nix
@@ -93,6 +93,9 @@ in
       "gnome-initial-setup.service"
     ];
 
+    programs.dconf.profiles.gnome-initial-setup.databases = [
+      "${pkgs.gnome.gnome-initial-setup}/share/gnome-initial-setup/initial-setup-dconf-defaults"
+    ];
   };
 
 }
diff --git a/nixos/modules/services/desktops/gvfs.nix b/nixos/modules/services/desktops/gvfs.nix
index 7e15b433fcc29..a4770d703f545 100644
--- a/nixos/modules/services/desktops/gvfs.nix
+++ b/nixos/modules/services/desktops/gvfs.nix
@@ -32,12 +32,7 @@ in
       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 = lib.mdDoc "Which GVfs package to use.";
-      };
+      package = mkPackageOption pkgs [ "gnome" "gvfs" ] { };
 
     };
 
diff --git a/nixos/modules/services/desktops/pipewire/pipewire.nix b/nixos/modules/services/desktops/pipewire/pipewire.nix
index ae695baf42c60..da409030b3a35 100644
--- a/nixos/modules/services/desktops/pipewire/pipewire.nix
+++ b/nixos/modules/services/desktops/pipewire/pipewire.nix
@@ -4,6 +4,8 @@
 with lib;
 
 let
+  json = pkgs.formats.json {};
+  mapToFiles = location: config: concatMapAttrs (name: value: { "pipewire/${location}.conf.d/${name}.conf".source = json.generate "${name}" value;}) config;
   cfg = config.services.pipewire;
   enable32BitAlsaPlugins = cfg.alsa.support32Bit
                            && pkgs.stdenv.isx86_64
@@ -25,14 +27,7 @@ in {
     services.pipewire = {
       enable = mkEnableOption (lib.mdDoc "pipewire service");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.pipewire;
-        defaultText = literalExpression "pkgs.pipewire";
-        description = lib.mdDoc ''
-          The pipewire derivation to use.
-        '';
-      };
+      package = mkPackageOption pkgs "pipewire" { };
 
       socketActivation = mkOption {
         default = true;
@@ -79,15 +74,140 @@ in {
           https://github.com/PipeWire/pipewire/blob/master/NEWS
         '';
       };
+
+      extraConfig = {
+        pipewire = mkOption {
+          type = lib.types.attrsOf json.type;
+          default = {};
+          example = {
+            "10-clock-rate" = {
+              "context.properties" = {
+                "default.clock.rate" = 44100;
+              };
+            };
+            "11-no-upmixing" = {
+              "stream.properties" = {
+                "channelmix.upmix" = false;
+              };
+            };
+          };
+          description = lib.mdDoc ''
+            Additional configuration for the PipeWire server.
+
+            Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/pipewire.conf.d`.
+
+            See `man pipewire.conf` for details, and [the PipeWire wiki][wiki] for examples.
+
+            See also:
+            - [PipeWire wiki - virtual devices][wiki-virtual-device] for creating virtual devices or remapping channels
+            - [PipeWire wiki - filter-chain][wiki-filter-chain] for creating more complex processing pipelines
+            - [PipeWire wiki - network][wiki-network] for streaming audio over a network
+
+            [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PipeWire
+            [wiki-virtual-device]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Virtual-Devices
+            [wiki-filter-chain]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Filter-Chain
+            [wiki-network]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Network
+          '';
+        };
+        client = mkOption {
+          type = lib.types.attrsOf json.type;
+          default = {};
+          example = {
+            "10-no-resample" = {
+              "stream.properties" = {
+                "resample.disable" = true;
+              };
+            };
+          };
+          description = lib.mdDoc ''
+            Additional configuration for the PipeWire client library, used by most applications.
+
+            Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/client.conf.d`.
+
+            See the [PipeWire wiki][wiki] for examples.
+
+            [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-client
+          '';
+        };
+        client-rt = mkOption {
+          type = lib.types.attrsOf json.type;
+          default = {};
+          example = {
+            "10-alsa-linear-volume" = {
+              "alsa.properties" = {
+                "alsa.volume-method" = "linear";
+              };
+            };
+          };
+          description = lib.mdDoc ''
+            Additional configuration for the PipeWire client library, used by real-time applications and legacy ALSA clients.
+
+            Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/client-rt.conf.d`.
+
+            See the [PipeWire wiki][wiki] for examples of general configuration, and [PipeWire wiki - ALSA][wiki-alsa] for ALSA clients.
+
+            [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-client
+            [wiki-alsa]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-ALSA
+          '';
+        };
+        jack = mkOption {
+          type = lib.types.attrsOf json.type;
+          default = {};
+          example = {
+            "20-hide-midi" = {
+              "jack.properties" = {
+                "jack.show-midi" = false;
+              };
+            };
+          };
+          description = lib.mdDoc ''
+            Additional configuration for the PipeWire JACK server and client library.
+
+            Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/jack.conf.d`.
+
+            See the [PipeWire wiki][wiki] for examples.
+
+            [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-JACK
+          '';
+        };
+        pipewire-pulse = mkOption {
+          type = lib.types.attrsOf json.type;
+          default = {};
+          example = {
+            "15-force-s16-info" = {
+              "pulse.rules" = [{
+                matches = [
+                  { "application.process.binary" = "my-broken-app"; }
+                ];
+                actions = {
+                  quirks = [ "force-s16-info" ];
+                };
+              }];
+            };
+          };
+          description = lib.mdDoc ''
+            Additional configuration for the PipeWire PulseAudio server.
+
+            Every item in this attrset becomes a separate drop-in file in `/etc/pipewire/pipewire-pulse.conf.d`.
+
+            See `man pipewire-pulse.conf` for details, and [the PipeWire wiki][wiki] for examples.
+
+            See also:
+            - [PipeWire wiki - PulseAudio tricks guide][wiki-tricks] for more examples.
+
+            [wiki]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Config-PulseAudio
+            [wiki-tricks]: https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/Guide-PulseAudio-Tricks
+          '';
+        };
+      };
     };
   };
 
   imports = [
     (lib.mkRemovedOptionModule ["services" "pipewire" "config"] ''
-      Overriding default Pipewire configuration through NixOS options never worked correctly and is no longer supported.
-      Please create drop-in files in /etc/pipewire/pipewire.conf.d/ to make the desired setting changes instead.
+      Overriding default PipeWire configuration through NixOS options never worked correctly and is no longer supported.
+      Please create drop-in configuration files via `services.pipewire.extraConfig` instead.
     '')
-
     (lib.mkRemovedOptionModule ["services" "pipewire" "media-session"] ''
       pipewire-media-session is no longer supported upstream and has been removed.
       Please switch to `services.pipewire.wireplumber` instead.
@@ -115,8 +235,7 @@ in {
     environment.systemPackages = [ cfg.package ]
                                  ++ lib.optional cfg.jack.enable jack-libs;
 
-    systemd.packages = [ cfg.package ]
-                       ++ lib.optional cfg.pulse.enable cfg.package.pulse;
+    systemd.packages = [ cfg.package ];
 
     # PipeWire depends on DBUS but doesn't list it. Without this booting
     # into a terminal results in the service crashing with an error.
@@ -130,33 +249,46 @@ in {
     systemd.user.sockets.pipewire.enable = !cfg.systemWide;
     systemd.user.services.pipewire.enable = !cfg.systemWide;
 
+    # Mask pw-pulse if it's not wanted
+    systemd.user.services.pipewire-pulse.enable = cfg.pulse.enable;
+    systemd.user.sockets.pipewire-pulse.enable = cfg.pulse.enable;
+
     systemd.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
     systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
-    systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf (cfg.socketActivation && cfg.pulse.enable) ["sockets.target"];
+    systemd.user.sockets.pipewire-pulse.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
 
     services.udev.packages = [ cfg.package ];
 
     # If any paths are updated here they must also be updated in the package test.
-    environment.etc."alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable {
-      text = ''
-        pcm_type.pipewire {
-          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;
-          ${optionalString enable32BitAlsaPlugins
-            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"}
-        }
-        ctl_type.pipewire {
-          libs.native = ${cfg.package.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;
-          ${optionalString enable32BitAlsaPlugins
-            "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire.lib}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"}
-        }
-      '';
-    };
-    environment.etc."alsa/conf.d/50-pipewire.conf" = mkIf cfg.alsa.enable {
-      source = "${cfg.package}/share/alsa/alsa.conf.d/50-pipewire.conf";
-    };
-    environment.etc."alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable {
-      source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf";
-    };
+    environment.etc = {
+      "alsa/conf.d/49-pipewire-modules.conf" = mkIf cfg.alsa.enable {
+        text = ''
+          pcm_type.pipewire {
+            libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;
+            ${optionalString enable32BitAlsaPlugins
+              "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_pcm_pipewire.so ;"}
+          }
+          ctl_type.pipewire {
+            libs.native = ${cfg.package}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;
+            ${optionalString enable32BitAlsaPlugins
+              "libs.32Bit = ${pkgs.pkgsi686Linux.pipewire}/lib/alsa-lib/libasound_module_ctl_pipewire.so ;"}
+          }
+        '';
+      };
+
+      "alsa/conf.d/50-pipewire.conf" = mkIf cfg.alsa.enable {
+        source = "${cfg.package}/share/alsa/alsa.conf.d/50-pipewire.conf";
+      };
+
+      "alsa/conf.d/99-pipewire-default.conf" = mkIf cfg.alsa.enable {
+        source = "${cfg.package}/share/alsa/alsa.conf.d/99-pipewire-default.conf";
+      };
+    }
+    // mapToFiles "pipewire" cfg.extraConfig.pipewire
+    // mapToFiles "client" cfg.extraConfig.client
+    // mapToFiles "client-rt" cfg.extraConfig.client-rt
+    // mapToFiles "jack" cfg.extraConfig.jack
+    // mapToFiles "pipewire-pulse" cfg.extraConfig.pipewire-pulse;
 
     environment.sessionVariables.LD_LIBRARY_PATH =
       lib.mkIf cfg.jack.enable [ "${cfg.package.jack}/lib" ];
diff --git a/nixos/modules/services/desktops/seatd.nix b/nixos/modules/services/desktops/seatd.nix
new file mode 100644
index 0000000000000..51977dfd2153a
--- /dev/null
+++ b/nixos/modules/services/desktops/seatd.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.seatd;
+  inherit (lib) mkEnableOption mkOption mdDoc types;
+in
+{
+  meta.maintainers = with lib.maintainers; [ sinanmohd ];
+
+  options.services.seatd = {
+    enable = mkEnableOption (mdDoc "seatd");
+
+    user = mkOption {
+      type = types.str;
+      default = "root";
+      description = mdDoc "User to own the seatd socket";
+    };
+    group = mkOption {
+      type = types.str;
+      default = "seat";
+      description = mdDoc "Group to own the seatd socket";
+    };
+    logLevel = mkOption {
+      type = types.enum [ "debug" "info" "error" "silent" ];
+      default = "info";
+      description = mdDoc "Logging verbosity";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ seatd sdnotify-wrapper ];
+    users.groups.seat = lib.mkIf (cfg.group == "seat") {};
+
+    systemd.services.seatd = {
+      description = "Seat management daemon";
+      documentation = [ "man:seatd(1)" ];
+
+      wantedBy = [ "multi-user.target" ];
+      restartIfChanged = false;
+
+      serviceConfig = {
+        Type = "notify";
+        NotifyAccess = "all";
+        SyslogIdentifier = "seatd";
+        ExecStart = "${pkgs.sdnotify-wrapper}/bin/sdnotify-wrapper ${pkgs.seatd.bin}/bin/seatd -n 1 -u ${cfg.user} -g ${cfg.group} -l ${cfg.logLevel}";
+        RestartSec = 1;
+        Restart = "always";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/development/athens.md b/nixos/modules/services/development/athens.md
new file mode 100644
index 0000000000000..77663db509d59
--- /dev/null
+++ b/nixos/modules/services/development/athens.md
@@ -0,0 +1,52 @@
+# Athens {#module-athens}
+
+*Source:* {file}`modules/services/development/athens.nix`
+
+*Upstream documentation:* <https://docs.gomods.io/>
+
+[Athens](https://github.com/gomods/athens)
+is a Go module datastore and proxy
+
+The main goal of Athens is providing a Go proxy (`$GOPROXY`) in regions without access to `https://proxy.golang.org` or to
+improve the speed of Go module downloads for CI/CD systems.
+
+## Configuring {#module-services-development-athens-configuring}
+
+A complete list of options for the Athens module may be found
+[here](#opt-services.athens.enable).
+
+## Basic usage for a caching proxy configuration {#opt-services-development-athens-caching-proxy}
+
+A very basic configuration for Athens that acts as a caching and forwarding HTTP proxy is:
+```
+{
+    services.athens = {
+      enable = true;
+    };
+}
+```
+
+If you want to prevent Athens from writing to disk, you can instead configure it to cache modules only in memory:
+
+```
+{
+    services.athens = {
+      enable = true;
+      storageType = "memory";
+    };
+}
+```
+
+To use the local proxy in Go builds, you can set the proxy as environment variable:
+
+```
+{
+  environment.variables = {
+    GOPROXY = "http://localhost:3000"
+  };
+}
+```
+
+It is currently not possible to use the local proxy for builds done by the Nix daemon. This might be enabled
+by experimental features, specifically [`configurable-impure-env`](https://nixos.org/manual/nix/unstable/contributing/experimental-features#xp-feature-configurable-impure-env),
+in upcoming Nix versions.
diff --git a/nixos/modules/services/development/athens.nix b/nixos/modules/services/development/athens.nix
new file mode 100644
index 0000000000000..34f8964a3bd55
--- /dev/null
+++ b/nixos/modules/services/development/athens.nix
@@ -0,0 +1,936 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.athens;
+
+  athensConfig = flip recursiveUpdate cfg.extraConfig (
+    {
+      GoBinary = "${cfg.goBinary}/bin/go";
+      GoEnv = cfg.goEnv;
+      GoBinaryEnvVars = lib.mapAttrsToList (k: v: "${k}=${v}") cfg.goBinaryEnvVars;
+      GoGetWorkers = cfg.goGetWorkers;
+      GoGetDir = cfg.goGetDir;
+      ProtocolWorkers = cfg.protocolWorkers;
+      LogLevel = cfg.logLevel;
+      CloudRuntime = cfg.cloudRuntime;
+      EnablePprof = cfg.enablePprof;
+      PprofPort = ":${toString cfg.pprofPort}";
+      FilterFile = cfg.filterFile;
+      RobotsFile = cfg.robotsFile;
+      Timeout = cfg.timeout;
+      StorageType = cfg.storageType;
+      TLSCertFile = cfg.tlsCertFile;
+      TLSKeyFile = cfg.tlsKeyFile;
+      Port = ":${toString cfg.port}";
+      UnixSocket = cfg.unixSocket;
+      GlobalEndpoint = cfg.globalEndpoint;
+      BasicAuthUser = cfg.basicAuthUser;
+      BasicAuthPass = cfg.basicAuthPass;
+      ForceSSL = cfg.forceSSL;
+      ValidatorHook = cfg.validatorHook;
+      PathPrefix = cfg.pathPrefix;
+      NETRCPath = cfg.netrcPath;
+      GithubToken = cfg.githubToken;
+      HGRCPath = cfg.hgrcPath;
+      TraceExporter = cfg.traceExporter;
+      StatsExporter = cfg.statsExporter;
+      SumDBs = cfg.sumDBs;
+      NoSumPatterns = cfg.noSumPatterns;
+      DownloadMode = cfg.downloadMode;
+      NetworkMode = cfg.networkMode;
+      DownloadURL = cfg.downloadURL;
+      SingleFlightType = cfg.singleFlightType;
+      IndexType = cfg.indexType;
+      ShutdownTimeout = cfg.shutdownTimeout;
+      SingleFlight = {
+        Etcd = {
+          Endpoints = builtins.concatStringsSep "," cfg.singleFlight.etcd.endpoints;
+        };
+        Redis = {
+          Endpoint = cfg.singleFlight.redis.endpoint;
+          Password = cfg.singleFlight.redis.password;
+          LockConfig = {
+            TTL = cfg.singleFlight.redis.lockConfig.ttl;
+            Timeout = cfg.singleFlight.redis.lockConfig.timeout;
+            MaxRetries = cfg.singleFlight.redis.lockConfig.maxRetries;
+          };
+        };
+        RedisSentinel = {
+          Endpoints = cfg.singleFlight.redisSentinel.endpoints;
+          MasterName = cfg.singleFlight.redisSentinel.masterName;
+          SentinelPassword = cfg.singleFlight.redisSentinel.sentinelPassword;
+          LockConfig = {
+            TTL = cfg.singleFlight.redisSentinel.lockConfig.ttl;
+            Timeout = cfg.singleFlight.redisSentinel.lockConfig.timeout;
+            MaxRetries = cfg.singleFlight.redisSentinel.lockConfig.maxRetries;
+          };
+        };
+      };
+      Storage = {
+        CDN = {
+          Endpoint = cfg.storage.cdn.endpoint;
+        };
+        Disk = {
+          RootPath = cfg.storage.disk.rootPath;
+        };
+        GCP = {
+          ProjectID = cfg.storage.gcp.projectID;
+          Bucket = cfg.storage.gcp.bucket;
+          JSONKey = cfg.storage.gcp.jsonKey;
+        };
+        Minio = {
+          Endpoint = cfg.storage.minio.endpoint;
+          Key = cfg.storage.minio.key;
+          Secret = cfg.storage.minio.secret;
+          EnableSSL = cfg.storage.minio.enableSSL;
+          Bucket = cfg.storage.minio.bucket;
+          region = cfg.storage.minio.region;
+        };
+        Mongo = {
+          URL = cfg.storage.mongo.url;
+          DefaultDBName = cfg.storage.mongo.defaultDBName;
+          CertPath = cfg.storage.mongo.certPath;
+          Insecure = cfg.storage.mongo.insecure;
+        };
+        S3 = {
+          Region = cfg.storage.s3.region;
+          Key = cfg.storage.s3.key;
+          Secret = cfg.storage.s3.secret;
+          Token = cfg.storage.s3.token;
+          Bucket = cfg.storage.s3.bucket;
+          ForcePathStyle = cfg.storage.s3.forcePathStyle;
+          UseDefaultConfiguration = cfg.storage.s3.useDefaultConfiguration;
+          CredentialsEndpoint = cfg.storage.s3.credentialsEndpoint;
+          AwsContainerCredentialsRelativeURI = cfg.storage.s3.awsContainerCredentialsRelativeURI;
+          Endpoint = cfg.storage.s3.endpoint;
+        };
+        AzureBlob = {
+          AccountName = cfg.storage.azureblob.accountName;
+          AccountKey = cfg.storage.azureblob.accountKey;
+          ContainerName = cfg.storage.azureblob.containerName;
+        };
+        External = {
+          URL = cfg.storage.external.url;
+        };
+      };
+      Index = {
+        MySQL = {
+          Protocol = cfg.index.mysql.protocol;
+          Host = cfg.index.mysql.host;
+          Port = cfg.index.mysql.port;
+          User = cfg.index.mysql.user;
+          Password = cfg.index.mysql.password;
+          Database = cfg.index.mysql.database;
+          Params = {
+            parseTime = cfg.index.mysql.params.parseTime;
+            timeout = cfg.index.mysql.params.timeout;
+          };
+        };
+        Postgres = {
+          Host = cfg.index.postgres.host;
+          Port = cfg.index.postgres.port;
+          User = cfg.index.postgres.user;
+          Password = cfg.index.postgres.password;
+          Database = cfg.index.postgres.database;
+          Params = {
+            connect_timeout = cfg.index.postgres.params.connect_timeout;
+            sslmode = cfg.index.postgres.params.sslmode;
+          };
+        };
+      };
+    }
+  );
+
+  configFile = pkgs.runCommandLocal "config.toml" { } ''
+    ${pkgs.buildPackages.jq}/bin/jq 'del(..|nulls)' \
+      < ${pkgs.writeText "config.json" (builtins.toJSON athensConfig)} | \
+    ${pkgs.buildPackages.remarshal}/bin/remarshal -if json -of toml \
+      > $out
+  '';
+in
+{
+  meta = {
+    maintainers = pkgs.athens.meta.maintainers;
+    doc = ./athens.md;
+  };
+
+  options.services.athens = {
+    enable = mkEnableOption (lib.mdDoc "Go module datastore and proxy");
+
+    package = mkOption {
+      default = pkgs.athens;
+      defaultText = literalExpression "pkgs.athens";
+      example = "pkgs.athens";
+      description = lib.mdDoc "Which athens derivation to use";
+      type = types.package;
+    };
+
+    goBinary = mkOption {
+      type = types.package;
+      default = pkgs.go;
+      defaultText = literalExpression "pkgs.go";
+      example = "pkgs.go_1_21";
+      description = lib.mdDoc ''
+        The Go package used by Athens at runtime.
+
+        Athens primarily runs two Go commands:
+        1. `go mod download -json <module>@<version>`
+        2. `go list -m -json <module>@latest`
+      '';
+    };
+
+    goEnv = mkOption {
+      type = types.enum [ "development" "production" ];
+      description = lib.mdDoc "Specifies the type of environment to run. One of 'development' or 'production'.";
+      default = "development";
+      example = "production";
+    };
+
+    goBinaryEnvVars = mkOption {
+      type = types.attrs;
+      description = lib.mdDoc "Environment variables to pass to the Go binary.";
+      example = ''
+        { "GOPROXY" = "direct", "GODEBUG" = "true" }
+      '';
+      default = { };
+    };
+
+    goGetWorkers = mkOption {
+      type = types.int;
+      description = lib.mdDoc "Number of workers concurrently downloading modules.";
+      default = 10;
+      example = 32;
+    };
+
+    goGetDir = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''
+        Temporary directory that Athens will use to
+        fetch modules from VCS prior to persisting
+        them to a storage backend.
+
+        If the value is empty, Athens will use the
+        default OS temp directory.
+      '';
+      default = null;
+      example = "/tmp/athens";
+    };
+
+    protocolWorkers = mkOption {
+      type = types.int;
+      description = lib.mdDoc "Number of workers concurrently serving protocol paths.";
+      default = 30;
+    };
+
+    logLevel = mkOption {
+      type = types.nullOr (types.enum [ "panic" "fatal" "error" "warning" "info" "debug" "trace" ]);
+      description = lib.mdDoc ''
+        Log level for Athens.
+        Supports all logrus log levels (https://github.com/Sirupsen/logrus#level-logging)".
+      '';
+      default = "warning";
+      example = "debug";
+    };
+
+    cloudRuntime = mkOption {
+      type = types.enum [ "GCP" "none" ];
+      description = lib.mdDoc ''
+        Specifies the Cloud Provider on which the Proxy/registry is running.
+      '';
+      default = "none";
+      example = "GCP";
+    };
+
+    enablePprof = mkOption {
+      type = types.bool;
+      description = lib.mdDoc "Enable pprof endpoints.";
+      default = false;
+    };
+
+    pprofPort = mkOption {
+      type = types.port;
+      description = lib.mdDoc "Port number for pprof endpoints.";
+      default = 3301;
+      example = 443;
+    };
+
+    filterFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''Filename for the include exclude filter.'';
+      default = null;
+      example = literalExpression ''
+        pkgs.writeText "filterFile" '''
+          - github.com/azure
+          + github.com/azure/azure-sdk-for-go
+          D golang.org/x/tools
+        '''
+      '';
+    };
+
+    robotsFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''Provides /robots.txt for net crawlers.'';
+      default = null;
+      example = literalExpression ''pkgs.writeText "robots.txt" "# my custom robots.txt ..."'';
+    };
+
+    timeout = mkOption {
+      type = types.int;
+      description = lib.mdDoc "Timeout for external network calls in seconds.";
+      default = 300;
+      example = 3;
+    };
+
+    storageType = mkOption {
+      type = types.enum [ "memory" "disk" "mongo" "gcp" "minio" "s3" "azureblob" "external" ];
+      description = lib.mdDoc "Specifies the type of storage backend to use.";
+      default = "disk";
+    };
+
+    tlsCertFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc "Path to the TLS certificate file.";
+      default = null;
+      example = "/etc/ssl/certs/athens.crt";
+    };
+
+    tlsKeyFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc "Path to the TLS key file.";
+      default = null;
+      example = "/etc/ssl/certs/athens.key";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 3000;
+      description = lib.mdDoc ''
+        Port number Athens listens on.
+      '';
+      example = 443;
+    };
+
+    unixSocket = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''
+        Path to the unix socket file.
+        If set, Athens will listen on the unix socket instead of TCP socket.
+      '';
+      default = null;
+      example = "/run/athens.sock";
+    };
+
+    globalEndpoint = mkOption {
+      type = types.str;
+      description = lib.mdDoc ''
+        Endpoint for a package registry in case of a proxy cache miss.
+      '';
+      default = "";
+      example = "http://upstream-athens.example.com:3000";
+    };
+
+    basicAuthUser = mkOption {
+      type = types.nullOr types.str;
+      description = lib.mdDoc ''
+        Username for basic auth.
+      '';
+      default = null;
+      example = "user";
+    };
+
+    basicAuthPass = mkOption {
+      type = types.nullOr types.str;
+      description = lib.mdDoc ''
+        Password for basic auth. Warning: this is stored in plain text in the config file.
+      '';
+      default = null;
+      example = "swordfish";
+    };
+
+    forceSSL = mkOption {
+      type = types.bool;
+      description = lib.mdDoc ''
+        Force SSL redirects for incoming requests.
+      '';
+      default = false;
+    };
+
+    validatorHook = mkOption {
+      type = types.nullOr types.str;
+      description = lib.mdDoc ''
+        Endpoint to validate modules against.
+
+        Not used if empty.
+      '';
+      default = null;
+      example = "https://validation.example.com";
+    };
+
+    pathPrefix = mkOption {
+      type = types.nullOr types.str;
+      description = lib.mdDoc ''
+        Sets basepath for all routes.
+      '';
+      default = null;
+      example = "/athens";
+    };
+
+    netrcPath = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''
+        Path to the .netrc file.
+      '';
+      default = null;
+      example = "/home/user/.netrc";
+    };
+
+    githubToken = mkOption {
+      type = types.nullOr types.str;
+      description = lib.mdDoc ''
+        Creates .netrc file with the given token to be used for GitHub.
+        Warning: this is stored in plain text in the config file.
+      '';
+      default = null;
+      example = "ghp_1234567890";
+    };
+
+    hgrcPath = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''
+        Path to the .hgrc file.
+      '';
+      default = null;
+      example = "/home/user/.hgrc";
+    };
+
+    traceExporter = mkOption {
+      type = types.nullOr (types.enum [ "jaeger" "datadog" ]);
+      description = lib.mdDoc ''
+        Trace exporter to use.
+      '';
+      default = null;
+    };
+
+    traceExporterURL = mkOption {
+      type = types.nullOr types.str;
+      description = lib.mdDoc ''
+        URL endpoint that traces will be sent to.
+      '';
+      default = null;
+      example = "http://localhost:14268";
+    };
+
+    statsExporter = mkOption {
+      type = types.nullOr (types.enum [ "prometheus" ]);
+      description = lib.mdDoc "Stats exporter to use.";
+      default = null;
+    };
+
+    sumDBs = mkOption {
+      type = types.listOf types.str;
+      description = lib.mdDoc ''
+        List of fully qualified URLs that Athens will proxy
+        that the go command can use a checksum verifier.
+      '';
+      default = [ "https://sum.golang.org" ];
+    };
+
+    noSumPatterns = mkOption {
+      type = types.listOf types.str;
+      description = lib.mdDoc ''
+        List of patterns that Athens sum db proxy will return a 403 for.
+      '';
+      default = [ ];
+      example = [ "github.com/mycompany/*" ];
+    };
+
+    downloadMode = mkOption {
+      type = types.oneOf [ (types.enum [ "sync" "async" "redirect" "async_redirect" "none" ]) (types.strMatching "^file:.*$|^custom:.*$") ];
+      description = lib.mdDoc ''
+        Defines how Athens behaves when a module@version
+        is not found in storage. There are 7 options:
+        1. "sync": download the module synchronously and
+        return the results to the client.
+        2. "async": return 404, but asynchronously store the module
+        in the storage backend.
+        3. "redirect": return a 301 redirect status to the client
+        with the base URL as the DownloadRedirectURL from below.
+        4. "async_redirect": same as option number 3 but it will
+        asynchronously store the module to the backend.
+        5. "none": return 404 if a module is not found and do nothing.
+        6. "file:<path>": will point to an HCL file that specifies
+        any of the 5 options above based on different import paths.
+        7. "custom:<base64-encoded-hcl>" is the same as option 6
+        but the file is fully encoded in the option. This is
+        useful for using an environment variable in serverless
+        deployments.
+      '';
+      default = "async_redirect";
+    };
+
+    networkMode = mkOption {
+      type = types.enum [ "strict" "offline" "fallback" ];
+      description = lib.mdDoc ''
+        Configures how Athens will return the results
+        of the /list endpoint as it can be assembled from both its own
+        storage and the upstream VCS.
+
+        Note, that for better error messaging, this would also affect how other
+        endpoints behave.
+
+        Modes:
+        1. strict: merge VCS versions with storage versions, but fail if either of them fails.
+        2. offline: only get storage versions, never reach out to VCS.
+        3. fallback: only return storage versions, if VCS fails. Note this means that you may
+        see inconsistent results since fallback mode does a best effort of giving you what's
+        available at the time of requesting versions.
+      '';
+      default = "strict";
+    };
+
+    downloadURL = mkOption {
+      type = types.str;
+      description = lib.mdDoc "URL used if DownloadMode is set to redirect.";
+      default = "https://proxy.golang.org";
+    };
+
+    singleFlightType = mkOption {
+      type = types.enum [ "memory" "etcd" "redis" "redis-sentinel" "gcp" "azureblob" ];
+      description = lib.mdDoc ''
+        Determines what mechanism Athens uses to manage concurrency flowing into the Athens backend.
+      '';
+      default = "memory";
+    };
+
+    indexType = mkOption {
+      type = types.enum [ "none" "memory" "mysql" "postgres" ];
+      description = lib.mdDoc ''
+        Type of index backend Athens will use.
+      '';
+      default = "none";
+    };
+
+    shutdownTimeout = mkOption {
+      type = types.int;
+      description = lib.mdDoc ''
+        Number of seconds to wait for the server to shutdown gracefully.
+      '';
+      default = 60;
+      example = 1;
+    };
+
+    singleFlight = {
+      etcd = {
+        endpoints = mkOption {
+          type = types.listOf types.str;
+          description = lib.mdDoc "URLs that determine all distributed etcd servers.";
+          default = [ ];
+          example = [ "localhost:2379" ];
+        };
+      };
+      redis = {
+        endpoint = mkOption {
+          type = types.str;
+          description = lib.mdDoc "URL of the redis server.";
+          default = "";
+          example = "localhost:6379";
+        };
+        password = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Password for the redis server. Warning: this is stored in plain text in the config file.";
+          default = "";
+          example = "swordfish";
+        };
+
+        lockConfig = {
+          ttl = mkOption {
+            type = types.int;
+            description = lib.mdDoc "TTL for the lock in seconds.";
+            default = 900;
+            example = 1;
+          };
+          timeout = mkOption {
+            type = types.int;
+            description = lib.mdDoc "Timeout for the lock in seconds.";
+            default = 15;
+            example = 1;
+          };
+          maxRetries = mkOption {
+            type = types.int;
+            description = lib.mdDoc "Maximum number of retries for the lock.";
+            default = 10;
+            example = 1;
+          };
+        };
+      };
+
+      redisSentinel = {
+        endpoints = mkOption {
+          type = types.listOf types.str;
+          description = lib.mdDoc "URLs that determine all distributed redis servers.";
+          default = [ ];
+          example = [ "localhost:26379" ];
+        };
+        masterName = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Name of the sentinel master server.";
+          default = "";
+          example = "redis-1";
+        };
+        sentinelPassword = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Password for the sentinel server. Warning: this is stored in plain text in the config file.";
+          default = "";
+          example = "swordfish";
+        };
+
+        lockConfig = {
+          ttl = mkOption {
+            type = types.int;
+            description = lib.mdDoc "TTL for the lock in seconds.";
+            default = 900;
+            example = 1;
+          };
+          timeout = mkOption {
+            type = types.int;
+            description = lib.mdDoc "Timeout for the lock in seconds.";
+            default = 15;
+            example = 1;
+          };
+          maxRetries = mkOption {
+            type = types.int;
+            description = lib.mdDoc "Maximum number of retries for the lock.";
+            default = 10;
+            example = 1;
+          };
+        };
+      };
+    };
+
+    storage = {
+      cdn = {
+        endpoint = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "hostname of the CDN server.";
+          example = "cdn.example.com";
+          default = null;
+        };
+      };
+
+      disk = {
+        rootPath = mkOption {
+          type = types.nullOr types.path;
+          description = lib.mdDoc "Athens disk root folder.";
+          default = "/var/lib/athens";
+        };
+      };
+
+      gcp = {
+        projectID = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "GCP project ID.";
+          example = "my-project";
+          default = null;
+        };
+        bucket = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "GCP backend storage bucket.";
+          example = "my-bucket";
+          default = null;
+        };
+        jsonKey = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Base64 encoded GCP service account key. Warning: this is stored in plain text in the config file.";
+          default = null;
+        };
+      };
+
+      minio = {
+        endpoint = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Endpoint of the minio storage backend.";
+          example = "minio.example.com:9001";
+          default = null;
+        };
+        key = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Access key id for the minio storage backend.";
+          example = "minio";
+          default = null;
+        };
+        secret = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Secret key for the minio storage backend. Warning: this is stored in plain text in the config file.";
+          example = "minio123";
+          default = null;
+        };
+        enableSSL = mkOption {
+          type = types.bool;
+          description = lib.mdDoc "Enable SSL for the minio storage backend.";
+          default = false;
+        };
+        bucket = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Bucket name for the minio storage backend.";
+          example = "gomods";
+          default = null;
+        };
+        region = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Region for the minio storage backend.";
+          example = "us-east-1";
+          default = null;
+        };
+      };
+
+      mongo = {
+        url = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "URL of the mongo database.";
+          example = "mongodb://localhost:27017";
+          default = null;
+        };
+        defaultDBName = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Name of the mongo database.";
+          example = "athens";
+          default = null;
+        };
+        certPath = mkOption {
+          type = types.nullOr types.path;
+          description = lib.mdDoc "Path to the certificate file for the mongo database.";
+          example = "/etc/ssl/mongo.pem";
+          default = null;
+        };
+        insecure = mkOption {
+          type = types.bool;
+          description = lib.mdDoc "Allow insecure connections to the mongo database.";
+          default = false;
+        };
+      };
+
+      s3 = {
+        region = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Region of the S3 storage backend.";
+          example = "eu-west-3";
+          default = null;
+        };
+        key = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Access key id for the S3 storage backend.";
+          example = "minio";
+          default = null;
+        };
+        secret = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Secret key for the S3 storage backend. Warning: this is stored in plain text in the config file.";
+          default = "";
+        };
+        token = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Token for the S3 storage backend. Warning: this is stored in plain text in the config file.";
+          default = null;
+        };
+        bucket = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Bucket name for the S3 storage backend.";
+          example = "gomods";
+          default = null;
+        };
+        forcePathStyle = mkOption {
+          type = types.bool;
+          description = lib.mdDoc "Force path style for the S3 storage backend.";
+          default = false;
+        };
+        useDefaultConfiguration = mkOption {
+          type = types.bool;
+          description = lib.mdDoc "Use default configuration for the S3 storage backend.";
+          default = false;
+        };
+        credentialsEndpoint = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Credentials endpoint for the S3 storage backend.";
+          default = "";
+        };
+        awsContainerCredentialsRelativeURI = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Container relative url (used by fargate).";
+          default = null;
+        };
+        endpoint = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Endpoint for the S3 storage backend.";
+          default = null;
+        };
+      };
+
+      azureblob = {
+        accountName = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Account name for the Azure Blob storage backend.";
+          default = null;
+        };
+        accountKey = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Account key for the Azure Blob storage backend. Warning: this is stored in plain text in the config file.";
+          default = null;
+        };
+        containerName = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Container name for the Azure Blob storage backend.";
+          default = null;
+        };
+      };
+
+      external = {
+        url = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "URL of the backend storage layer.";
+          example = "https://athens.example.com";
+          default = null;
+        };
+      };
+    };
+
+    index = {
+      mysql = {
+        protocol = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Protocol for the MySQL database.";
+          default = "tcp";
+        };
+        host = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Host for the MySQL database.";
+          default = "localhost";
+        };
+        port = mkOption {
+          type = types.int;
+          description = lib.mdDoc "Port for the MySQL database.";
+          default = 3306;
+        };
+        user = mkOption {
+          type = types.str;
+          description = lib.mdDoc "User for the MySQL database.";
+          default = "root";
+        };
+        password = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Password for the MySQL database. Warning: this is stored in plain text in the config file.";
+          default = null;
+        };
+        database = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Database name for the MySQL database.";
+          default = "athens";
+        };
+        params = {
+          parseTime = mkOption {
+            type = types.nullOr types.str;
+            description = lib.mdDoc "Parse time for the MySQL database.";
+            default = "true";
+          };
+          timeout = mkOption {
+            type = types.nullOr types.str;
+            description = lib.mdDoc "Timeout for the MySQL database.";
+            default = "30s";
+          };
+        };
+      };
+
+      postgres = {
+        host = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Host for the Postgres database.";
+          default = "localhost";
+        };
+        port = mkOption {
+          type = types.int;
+          description = lib.mdDoc "Port for the Postgres database.";
+          default = 5432;
+        };
+        user = mkOption {
+          type = types.str;
+          description = lib.mdDoc "User for the Postgres database.";
+          default = "postgres";
+        };
+        password = mkOption {
+          type = types.nullOr types.str;
+          description = lib.mdDoc "Password for the Postgres database. Warning: this is stored in plain text in the config file.";
+          default = null;
+        };
+        database = mkOption {
+          type = types.str;
+          description = lib.mdDoc "Database name for the Postgres database.";
+          default = "athens";
+        };
+        params = {
+          connect_timeout = mkOption {
+            type = types.nullOr types.str;
+            description = lib.mdDoc "Connect timeout for the Postgres database.";
+            default = "30s";
+          };
+          sslmode = mkOption {
+            type = types.nullOr types.str;
+            description = lib.mdDoc "SSL mode for the Postgres database.";
+            default = "disable";
+          };
+        };
+      };
+    };
+
+    extraConfig = mkOption {
+      type = types.attrs;
+      description = lib.mdDoc ''
+        Extra configuration options for the athens config file.
+      '';
+      default = { };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.athens = {
+      description = "Athens Go module proxy";
+      documentation = [ "https://docs.gomods.io" ];
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+
+      serviceConfig = {
+        Restart = "on-abnormal";
+        Nice = 5;
+        ExecStart = ''${cfg.package}/bin/athens -config_file=${configFile}'';
+
+        KillMode = "mixed";
+        KillSignal = "SIGINT";
+        TimeoutStopSec = cfg.shutdownTimeout;
+
+        LimitNOFILE = 1048576;
+        LimitNPROC = 512;
+
+        DynamicUser = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHome = "read-only";
+        ProtectSystem = "full";
+
+        ReadWritePaths = mkIf (cfg.storage.disk.rootPath != null && (! hasPrefix "/var/lib/" cfg.storage.disk.rootPath)) [ cfg.storage.disk.rootPath ];
+        StateDirectory = mkIf (hasPrefix "/var/lib/" cfg.storage.disk.rootPath) [ (removePrefix "/var/lib/" cfg.storage.disk.rootPath) ];
+
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        NoNewPrivileges = true;
+      };
+    };
+
+    networking.firewall = {
+      allowedTCPPorts = optionals (cfg.unixSocket == null) [ cfg.port ]
+        ++ optionals cfg.enablePprof [ cfg.pprofPort ];
+    };
+  };
+
+}
diff --git a/nixos/modules/services/development/distccd.nix b/nixos/modules/services/development/distccd.nix
index a3c909eb1959a..c33bf436bffb5 100644
--- a/nixos/modules/services/development/distccd.nix
+++ b/nixos/modules/services/development/distccd.nix
@@ -66,14 +66,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.distcc;
-        defaultText = literalExpression "pkgs.distcc";
-        description = lib.mdDoc ''
-          The distcc package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "distcc" { };
 
       port = mkOption {
         type = types.port;
diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix
index 9f79108444685..da8c7547fdd79 100644
--- a/nixos/modules/services/development/jupyter/default.nix
+++ b/nixos/modules/services/development/jupyter/default.nix
@@ -34,17 +34,10 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      # NOTE: We don't use top-level jupyter because we don't
-      # want to pass in JUPYTER_PATH but use .environment instead,
-      # saving a rebuild.
-      default = pkgs.python3.pkgs.notebook;
-      defaultText = literalExpression "pkgs.python3.pkgs.notebook";
-      description = lib.mdDoc ''
-        Jupyter package to use.
-      '';
-    };
+    # NOTE: We don't use top-level jupyter because we don't
+    # want to pass in JUPYTER_PATH but use .environment instead,
+    # saving a rebuild.
+    package = mkPackageOption pkgs [ "python3" "pkgs" "notebook" ] { };
 
     command = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/development/livebook.md b/nixos/modules/services/development/livebook.md
new file mode 100644
index 0000000000000..5012e977a4f7f
--- /dev/null
+++ b/nixos/modules/services/development/livebook.md
@@ -0,0 +1,50 @@
+# Livebook {#module-services-livebook}
+
+[Livebook](https://livebook.dev/) is a web application for writing
+interactive and collaborative code notebooks.
+
+## Basic Usage {#module-services-livebook-basic-usage}
+
+Enabling the `livebook` service creates a user
+[`systemd`](https://www.freedesktop.org/wiki/Software/systemd/) unit
+which runs the server.
+
+```
+{ ... }:
+
+{
+  services.livebook = {
+    enableUserService = true;
+    port = 20123;
+    # See note below about security
+    environmentFile = pkgs.writeText "livebook.env" ''
+      LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+    '';
+  };
+}
+```
+
+::: {.note}
+
+The Livebook server has the ability to run any command as the user it
+is running under, so securing access to it with a password is highly
+recommended.
+
+Putting the password in the Nix configuration like above is an easy
+way to get started but it is not recommended in the real world because
+the `livebook.env` file will be added to the world-readable Nix store.
+A better approach would be to put the password in some secure
+user-readable location and set `environmentFile = /home/user/secure/livebook.env`.
+
+:::
+
+### Extra dependencies {#module-services-livebook-extra-dependencies}
+
+By default, the Livebook service is run with minimum dependencies, but
+some features require additional packages.  For example, the machine
+learning Kinos require `gcc` and `gnumake`.  To add these, use
+`extraPackages`:
+
+```
+services.livebook.extraPackages = with pkgs; [ gcc gnumake ];
+```
diff --git a/nixos/modules/services/development/livebook.nix b/nixos/modules/services/development/livebook.nix
new file mode 100644
index 0000000000000..75729ff28efaf
--- /dev/null
+++ b/nixos/modules/services/development/livebook.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.livebook;
+in
+{
+  options.services.livebook = {
+    # Since livebook doesn't have a granular permission system (a user
+    # either has access to all the data or none at all), the decision
+    # was made to run this as a user service.  If that changes in the
+    # future, this can be changed to a system service.
+    enableUserService = mkEnableOption "a user service for Livebook";
+
+    package = mkPackageOption pkgs "livebook" { };
+
+    environmentFile = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Environment file as defined in {manpage}`systemd.exec(5)` passed to the service.
+
+        This must contain at least `LIVEBOOK_PASSWORD` or
+        `LIVEBOOK_TOKEN_ENABLED=false`.  See `livebook server --help`
+        for other options.'';
+    };
+
+    erlang_node_short_name = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "livebook";
+      description = "A short name for the distributed node.";
+    };
+
+    erlang_node_name = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "livebook@127.0.0.1";
+      description = "The name for the app distributed node.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = "The port to start the web application on.";
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = lib.mdDoc ''
+        The address to start the web application on.  Must be a valid IPv4 or
+        IPv6 address.
+      '';
+    };
+
+    options = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      description = lib.mdDoc ''
+        Additional options to pass as command-line arguments to the server.
+      '';
+      example = literalExpression ''
+        {
+          cookie = "a value shared by all nodes in this cluster";
+        }
+      '';
+    };
+
+    extraPackages = mkOption {
+      type = with types; listOf package;
+      default = [ ];
+      description = lib.mdDoc ''
+        Extra packages to make available to the Livebook service.
+      '';
+      example = literalExpression "with pkgs; [ gcc gnumake ]";
+    };
+  };
+
+  config = mkIf cfg.enableUserService {
+    systemd.user.services.livebook = {
+      serviceConfig = {
+        Restart = "always";
+        EnvironmentFile = cfg.environmentFile;
+        ExecStart =
+          let
+            args = lib.cli.toGNUCommandLineShell { } ({
+              inherit (cfg) port;
+              ip = cfg.address;
+              name = cfg.erlang_node_name;
+              sname = cfg.erlang_node_short_name;
+            } // cfg.options);
+          in
+            "${cfg.package}/bin/livebook server ${args}";
+      };
+      path = [ pkgs.bash ] ++ cfg.extraPackages;
+      wantedBy = [ "default.target" ];
+    };
+  };
+
+  meta.doc = ./livebook.md;
+}
diff --git a/nixos/modules/services/development/nixseparatedebuginfod.nix b/nixos/modules/services/development/nixseparatedebuginfod.nix
new file mode 100644
index 0000000000000..daf85153d339f
--- /dev/null
+++ b/nixos/modules/services/development/nixseparatedebuginfod.nix
@@ -0,0 +1,105 @@
+{ pkgs, lib, config, ... }:
+let
+  cfg = config.services.nixseparatedebuginfod;
+  url = "127.0.0.1:${toString cfg.port}";
+in
+{
+  options = {
+    services.nixseparatedebuginfod = {
+      enable = lib.mkEnableOption "separatedebuginfod, a debuginfod server providing source and debuginfo for nix packages";
+      port = lib.mkOption {
+        description = "port to listen";
+        default = 1949;
+        type = lib.types.port;
+      };
+      nixPackage = lib.mkOption {
+        type = lib.types.package;
+        default = pkgs.nix;
+        defaultText = lib.literalExpression "pkgs.nix";
+        description = ''
+          The version of nix that nixseparatedebuginfod should use as client for the nix daemon. It is strongly advised to use nix version >= 2.18, otherwise some debug info may go missing.
+        '';
+      };
+      allowOldNix = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = ''
+          Do not fail evaluation when {option}`services.nixseparatedebuginfod.nixPackage` is older than nix 2.18.
+        '';
+      };
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    assertions = [ {
+      assertion = cfg.allowOldNix || (lib.versionAtLeast cfg.nixPackage.version "2.18");
+      message = "nixseparatedebuginfod works better when `services.nixseparatedebuginfod.nixPackage` is set to nix >= 2.18 (instead of ${cfg.nixPackage.name}). Set `services.nixseparatedebuginfod.allowOldNix` to bypass.";
+    } ];
+
+    systemd.services.nixseparatedebuginfod = {
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "nix-daemon.service" ];
+      after = [ "nix-daemon.service" ];
+      path = [ cfg.nixPackage ];
+      serviceConfig = {
+        ExecStart = [ "${pkgs.nixseparatedebuginfod}/bin/nixseparatedebuginfod -l ${url}" ];
+        Restart = "on-failure";
+        CacheDirectory = "nixseparatedebuginfod";
+        # nix does not like DynamicUsers in allowed-users
+        User = "nixseparatedebuginfod";
+        Group = "nixseparatedebuginfod";
+
+        # hardening
+        # Filesystem stuff
+        ProtectSystem = "strict"; # Prevent writing to most of /
+        ProtectHome = true; # Prevent accessing /home and /root
+        PrivateTmp = true; # Give an own directory under /tmp
+        PrivateDevices = true; # Deny access to most of /dev
+        ProtectKernelTunables = true; # Protect some parts of /sys
+        ProtectControlGroups = true; # Remount cgroups read-only
+        RestrictSUIDSGID = true; # Prevent creating SETUID/SETGID files
+        PrivateMounts = true; # Give an own mount namespace
+        RemoveIPC = true;
+        UMask = "0077";
+
+        # Capabilities
+        CapabilityBoundingSet = ""; # Allow no capabilities at all
+        NoNewPrivileges = true; # Disallow getting more capabilities. This is also implied by other options.
+
+        # Kernel stuff
+        ProtectKernelModules = true; # Prevent loading of kernel modules
+        SystemCallArchitectures = "native"; # Usually no need to disable this
+        ProtectKernelLogs = true; # Prevent access to kernel logs
+        ProtectClock = true; # Prevent setting the RTC
+
+        # Networking
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+
+        # Misc
+        LockPersonality = true; # Prevent change of the personality
+        ProtectHostname = true; # Give an own UTS namespace
+        RestrictRealtime = true; # Prevent switching to RT scheduling
+        MemoryDenyWriteExecute = true; # Maybe disable this for interpreters like python
+        RestrictNamespaces = true;
+      };
+    };
+
+    users.users.nixseparatedebuginfod = {
+      isSystemUser = true;
+      group = "nixseparatedebuginfod";
+    };
+
+    users.groups.nixseparatedebuginfod = { };
+
+    nix.settings.extra-allowed-users = [ "nixseparatedebuginfod" ];
+
+    environment.variables.DEBUGINFOD_URLS = "http://${url}";
+
+    environment.systemPackages = [
+      # valgrind support requires debuginfod-find on PATH
+      (lib.getBin pkgs.elfutils)
+    ];
+
+    environment.etc."gdb/gdbinit.d/nixseparatedebuginfod.gdb".text = "set debuginfod enabled on";
+
+  };
+}
diff --git a/nixos/modules/services/development/rstudio-server/default.nix b/nixos/modules/services/development/rstudio-server/default.nix
index bf4c7727bf748..fc3756edf0abc 100644
--- a/nixos/modules/services/development/rstudio-server/default.nix
+++ b/nixos/modules/services/development/rstudio-server/default.nix
@@ -39,14 +39,8 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.rstudio-server;
-      defaultText = literalExpression "pkgs.rstudio-server";
-      example = literalExpression "pkgs.rstudioServerWrapper.override { packages = [ pkgs.rPackages.ggplot2 ]; }";
-      description = lib.mdDoc ''
-        Rstudio server package to use. Can be set to rstudioServerWrapper to provide packages.
-      '';
+    package = mkPackageOption pkgs "rstudio-server" {
+      example = "rstudioServerWrapper.override { packages = [ pkgs.rPackages.ggplot2 ]; }";
     };
 
     rserverExtraConfig = mkOption {
diff --git a/nixos/modules/services/development/zammad.nix b/nixos/modules/services/development/zammad.nix
index 7dd143eebf12f..c084d6541ad38 100644
--- a/nixos/modules/services/development/zammad.nix
+++ b/nixos/modules/services/development/zammad.nix
@@ -21,6 +21,7 @@ let
     NODE_ENV = "production";
     RAILS_SERVE_STATIC_FILES = "true";
     RAILS_LOG_TO_STDOUT = "true";
+    REDIS_URL = "redis://${cfg.redis.host}:${toString cfg.redis.port}";
   };
   databaseConfig = settingsFormat.generate "database.yml" cfg.database.settings;
 in
@@ -30,12 +31,7 @@ in
     services.zammad = {
       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 = lib.mdDoc "Zammad package to use.";
-      };
+      package = mkPackageOption pkgs "zammad" { };
 
       dataDir = mkOption {
         type = types.path;
@@ -70,6 +66,36 @@ in
         description = lib.mdDoc "Websocket service port.";
       };
 
+      redis = {
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc "Whether to create a local redis automatically.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zammad";
+          description = lib.mdDoc ''
+            Name of the redis server. Only used if `createLocally` is set to true.
+          '';
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = lib.mdDoc ''
+            Redis server address.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 6379;
+          description = lib.mdDoc "Port of the redis server.";
+        };
+      };
+
       database = {
         type = mkOption {
           type = types.enum [ "PostgreSQL" "MySQL" ];
@@ -204,13 +230,17 @@ in
 
     assertions = [
       {
-        assertion = cfg.database.createLocally -> cfg.database.user == "zammad";
+        assertion = cfg.database.createLocally -> cfg.database.user == "zammad" && cfg.database.name == "zammad";
         message = "services.zammad.database.user must be set to \"zammad\" if services.zammad.database.createLocally is set to true";
       }
       {
         assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
         message = "a password cannot be specified if services.zammad.database.createLocally is set to true";
       }
+      {
+        assertion = cfg.redis.createLocally -> cfg.redis.host == "localhost";
+        message = "the redis host must be localhost if services.zammad.redis.createLocally is set to true";
+      }
     ];
 
     services.mysql = optionalAttrs (cfg.database.createLocally && cfg.database.type == "MySQL") {
@@ -231,11 +261,18 @@ in
       ensureUsers = [
         {
           name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
 
+    services.redis = optionalAttrs cfg.redis.createLocally {
+      servers."${cfg.redis.name}" = {
+        enable = true;
+        port = cfg.redis.port;
+      };
+    };
+
     systemd.services.zammad-web = {
       inherit environment;
       serviceConfig = serviceConfig // {
@@ -245,6 +282,8 @@ in
       after = [
         "network.target"
         "postgresql.service"
+      ] ++ optionals cfg.redis.createLocally [
+        "redis-${cfg.redis.name}.service"
       ];
       requires = [
         "postgresql.service"
@@ -308,16 +347,15 @@ in
       script = "./script/websocket-server.rb -b ${cfg.host} -p ${toString cfg.websocketPort} start";
     };
 
-    systemd.services.zammad-scheduler = {
-      inherit environment;
-      serviceConfig = serviceConfig // { Type = "forking"; };
+    systemd.services.zammad-worker = {
+      inherit serviceConfig environment;
       after = [ "zammad-web.service" ];
       requires = [ "zammad-web.service" ];
-      description = "Zammad scheduler";
+      description = "Zammad background worker";
       wantedBy = [ "multi-user.target" ];
-      script = "./script/scheduler.rb start";
+      script = "./script/background-worker.rb start";
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ garbas taeer ];
+  meta.maintainers = with lib.maintainers; [ taeer netali ];
 }
diff --git a/nixos/modules/services/display-managers/greetd.nix b/nixos/modules/services/display-managers/greetd.nix
index 3a0f59f62afbd..2212f97a9ffe2 100644
--- a/nixos/modules/services/display-managers/greetd.nix
+++ b/nixos/modules/services/display-managers/greetd.nix
@@ -4,18 +4,13 @@ with lib;
 let
   cfg = config.services.greetd;
   tty = "tty${toString cfg.vt}";
-  settingsFormat = pkgs.formats.toml {};
+  settingsFormat = pkgs.formats.toml { };
 in
 {
   options.services.greetd = {
     enable = mkEnableOption (lib.mdDoc "greetd");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.greetd.greetd;
-      defaultText = literalExpression "pkgs.greetd.greetd";
-      description = lib.mdDoc "The greetd package that should be used.";
-    };
+    package = mkPackageOption pkgs [ "greetd" "greetd" ] { };
 
     settings = mkOption {
       type = settingsFormat.type;
@@ -32,7 +27,7 @@ in
       '';
     };
 
-    vt = mkOption  {
+    vt = mkOption {
       type = types.int;
       default = 1;
       description = lib.mdDoc ''
@@ -59,6 +54,7 @@ in
     security.pam.services.greetd = {
       allowNullPassword = true;
       startSession = true;
+      enableGnomeKeyring = mkDefault config.services.gnome.gnome-keyring.enable;
     };
 
     # This prevents nixos-rebuild from killing greetd by activating getty again
@@ -101,12 +97,18 @@ in
 
     systemd.defaultUnit = "graphical.target";
 
+    # Create directories potentially required by supported greeters
+    # See https://github.com/NixOS/nixpkgs/issues/248323
+    systemd.tmpfiles.rules = [
+      "d '/var/cache/tuigreet' - greeter greeter - -"
+    ];
+
     users.users.greeter = {
       isSystemUser = true;
       group = "greeter";
     };
 
-    users.groups.greeter = {};
+    users.groups.greeter = { };
   };
 
   meta.maintainers = with maintainers; [ queezle ];
diff --git a/nixos/modules/services/editors/emacs.md b/nixos/modules/services/editors/emacs.md
index 9db1bd5941750..02f47b098d86c 100644
--- a/nixos/modules/services/editors/emacs.md
+++ b/nixos/modules/services/editors/emacs.md
@@ -172,9 +172,9 @@ nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.orgPackages
 :::
 
 If you are on NixOS, you can install this particular Emacs for all users by
-adding it to the list of system packages (see
-[](#sec-declarative-package-mgmt)). Simply modify your file
-{file}`configuration.nix` to make it contain:
+putting the `emacs.nix` file in `/etc/nixos` and adding it to the list of
+system packages (see [](#sec-declarative-package-mgmt)). Simply modify your
+file {file}`configuration.nix` to make it contain:
 ::: {.example #module-services-emacs-configuration-nix}
 ### Custom Emacs in `configuration.nix`
 
@@ -182,7 +182,7 @@ adding it to the list of system packages (see
 {
  environment.systemPackages = [
    # [...]
-   (import /path/to/emacs.nix { inherit pkgs; })
+   (import ./emacs.nix { inherit pkgs; })
   ];
 }
 ```
@@ -197,8 +197,8 @@ https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
 -->
 
 If you are not on NixOS or want to install this particular Emacs only for
-yourself, you can do so by adding it to your
-{file}`~/.config/nixpkgs/config.nix` (see
+yourself, you can do so by putting `emacs.nix` in `~/.config/nixpkgs` and
+adding it to your {file}`~/.config/nixpkgs/config.nix` (see
 [Nixpkgs manual](https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides)):
 ::: {.example #module-services-emacs-config-nix}
 ### Custom Emacs in `~/.config/nixpkgs/config.nix`
@@ -206,7 +206,7 @@ yourself, you can do so by adding it to your
 ```
 {
   packageOverrides = super: let self = super.pkgs; in {
-    myemacs = import /path/to/emacs.nix { pkgs = self; };
+    myemacs = import ./emacs.nix { pkgs = self; };
   };
 }
 ```
@@ -264,7 +264,6 @@ To install and enable the {command}`systemd` user service for Emacs
 daemon, add the following to your {file}`configuration.nix`:
 ```
 services.emacs.enable = true;
-services.emacs.package = import /home/cassou/.emacs.d { pkgs = pkgs; };
 ```
 
 The {var}`services.emacs.package` option allows a custom
diff --git a/nixos/modules/services/editors/emacs.nix b/nixos/modules/services/editors/emacs.nix
index fad4f39ff2104..ff6fd85d8a9b7 100644
--- a/nixos/modules/services/editors/emacs.nix
+++ b/nixos/modules/services/editors/emacs.nix
@@ -15,25 +15,6 @@ let
     fi
   '';
 
-  desktopApplicationFile = pkgs.writeTextFile {
-    name = "emacsclient.desktop";
-    destination = "/share/applications/emacsclient.desktop";
-    text = ''
-      [Desktop Entry]
-      Name=Emacsclient
-      GenericName=Text Editor
-      Comment=Edit text
-      MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
-      Exec=emacseditor %F
-      Icon=emacs
-      Type=Application
-      Terminal=false
-      Categories=Development;TextEditor;
-      StartupWMClass=Emacs
-      Keywords=Text;Editor;
-    '';
-  };
-
 in
 {
 
@@ -63,14 +44,7 @@ in
     };
 
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.emacs;
-      defaultText = literalExpression "pkgs.emacs";
-      description = lib.mdDoc ''
-        emacs derivation to use.
-      '';
-    };
+    package = mkPackageOption pkgs "emacs" { };
 
     defaultEditor = mkOption {
       type = types.bool;
@@ -109,7 +83,7 @@ in
       wantedBy = if cfg.startWithGraphical then [ "graphical-session.target" ] else [ "default.target" ];
     };
 
-    environment.systemPackages = [ cfg.package editorScript desktopApplicationFile ];
+    environment.systemPackages = [ cfg.package editorScript ];
 
     environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "emacseditor");
   };
diff --git a/nixos/modules/services/editors/infinoted.nix b/nixos/modules/services/editors/infinoted.nix
index de09899940199..976163d4d0b20 100644
--- a/nixos/modules/services/editors/infinoted.nix
+++ b/nixos/modules/services/editors/infinoted.nix
@@ -8,14 +8,7 @@ in {
   options.services.infinoted = {
     enable = mkEnableOption (lib.mdDoc "infinoted");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.libinfinity;
-      defaultText = literalExpression "pkgs.libinfinity";
-      description = lib.mdDoc ''
-        Package providing infinoted
-      '';
-    };
+    package = mkPackageOption pkgs "libinfinity" { };
 
     keyFile = mkOption {
       type = types.nullOr types.path;
diff --git a/nixos/modules/services/finance/odoo.nix b/nixos/modules/services/finance/odoo.nix
index eec7c4e30cc48..aa9bd0014d985 100644
--- a/nixos/modules/services/finance/odoo.nix
+++ b/nixos/modules/services/finance/odoo.nix
@@ -11,12 +11,7 @@ in
     services.odoo = {
       enable = mkEnableOption (lib.mdDoc "odoo");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.odoo;
-        defaultText = literalExpression "pkgs.odoo";
-        description = lib.mdDoc "Odoo package to use.";
-      };
+      package = mkPackageOption pkgs "odoo" { };
 
       addons = mkOption {
         type = with types; listOf package;
@@ -121,7 +116,7 @@ in
       ensureDatabases = [ "odoo" ];
       ensureUsers = [{
         name = "odoo";
-        ensurePermissions = { "DATABASE odoo" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
     };
   });
diff --git a/nixos/modules/services/games/asf.nix b/nixos/modules/services/games/archisteamfarm.nix
index f15d7077d965c..293e341bef385 100644
--- a/nixos/modules/services/games/asf.nix
+++ b/nixos/modules/services/games/archisteamfarm.nix
@@ -1,13 +1,11 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.services.archisteamfarm;
 
   format = pkgs.formats.json { };
 
-  asf-config = format.generate "ASF.json" (cfg.settings // {
+  configFile = format.generate "ASF.json" (cfg.settings // {
     # we disable it because ASF cannot update itself anyways
     # and nixos takes care of restarting the service
     # is in theory not needed as this is already the default for default builds
@@ -30,8 +28,8 @@ let
 in
 {
   options.services.archisteamfarm = {
-    enable = mkOption {
-      type = types.bool;
+    enable = lib.mkOption {
+      type = lib.types.bool;
       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.
@@ -40,19 +38,19 @@ in
       default = false;
     };
 
-    web-ui = mkOption {
-      type = types.submodule {
+    web-ui = lib.mkOption {
+      type = lib.types.submodule {
         options = {
-          enable = mkEnableOption "" // {
+          enable = lib.mkEnableOption "" // {
             description = lib.mdDoc "Whether to start the web-ui. This is the preferred way of configuring things such as the steam guard token.";
           };
 
-          package = mkOption {
-            type = types.package;
-            default = pkgs.ArchiSteamFarm.ui;
-            defaultText = lib.literalExpression "pkgs.ArchiSteamFarm.ui";
-            description =
-              lib.mdDoc "Web-UI package to use. Contents must be in lib/dist.";
+          package = lib.mkPackageOption pkgs [ "ArchiSteamFarm" "ui" ] {
+            extraDescription = ''
+              ::: {.note}
+              Contents must be in lib/dist
+              :::
+            '';
           };
         };
       };
@@ -65,23 +63,24 @@ in
       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 =
-        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.";
+    package = lib.mkPackageOption pkgs "ArchiSteamFarm" {
+      extraDescription = ''
+        ::: {.warning}
+        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";
+    dataDir = lib.mkOption {
+      type = lib.types.path;
+      default = "/var/lib/archisteamfarm";
       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 {
+    settings = lib.mkOption {
       type = format.type;
       description = lib.mdDoc ''
         The ASF.json file, all the options are documented [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#global-config).
@@ -95,13 +94,13 @@ in
       default = { };
     };
 
-    ipcPasswordFile = mkOption {
-      type = types.nullOr types.path;
+    ipcPasswordFile = lib.mkOption {
+      type = with lib.types; nullOr path;
       default = null;
-      description = lib.mdDoc "Path to a file containing the password. The file must be readable by the `asf` user/group.";
+      description = lib.mdDoc "Path to a file containing the password. The file must be readable by the `archisteamfarm` user/group.";
     };
 
-    ipcSettings = mkOption {
+    ipcSettings = lib.mkOption {
       type = format.type;
       description = lib.mdDoc ''
         Settings to write to IPC.config.
@@ -119,25 +118,25 @@ in
       default = { };
     };
 
-    bots = mkOption {
-      type = types.attrsOf (types.submodule {
+    bots = lib.mkOption {
+      type = lib.types.attrsOf (lib.types.submodule {
         options = {
-          username = mkOption {
-            type = types.str;
+          username = lib.mkOption {
+            type = lib.types.str;
             description = lib.mdDoc "Name of the user to log in. Default is attribute name.";
             default = "";
           };
-          passwordFile = mkOption {
-            type = types.path;
-            description = lib.mdDoc "Path to a file containing the password. The file must be readable by the `asf` user/group.";
+          passwordFile = lib.mkOption {
+            type = lib.types.path;
+            description = lib.mdDoc "Path to a file containing the password. The file must be readable by the `archisteamfarm` user/group.";
           };
-          enabled = mkOption {
-            type = types.bool;
+          enabled = lib.mkOption {
+            type = lib.types.bool;
             default = true;
             description = lib.mdDoc "Whether to enable the bot on startup.";
           };
-          settings = mkOption {
-            type = types.attrs;
+          settings = lib.mkOption {
+            type = lib.types.attrs;
             description = lib.mdDoc ''
               Additional settings that are documented [here](https://github.com/JustArchiNET/ArchiSteamFarm/wiki/Configuration#bot-config).
             '';
@@ -151,7 +150,7 @@ in
       example = {
         exampleBot = {
           username = "alice";
-          passwordFile = "/var/lib/asf/secrets/password";
+          passwordFile = "/var/lib/archisteamfarm/secrets/password";
           settings = { SteamParentalCode = "1234"; };
         };
       };
@@ -159,57 +158,69 @@ in
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
+    # TODO: drop with 24.11
+    services.archisteamfarm.dataDir = lib.mkIf (lib.versionAtLeast config.system.stateVersion "24.05") (lib.mkDefault "/var/lib/asf");
 
     users = {
-      users.asf = {
+      users.archisteamfarm = {
         home = cfg.dataDir;
         isSystemUser = true;
-        group = "asf";
+        group = "archisteamfarm";
         description = "Archis-Steam-Farm service user";
       };
-      groups.asf = { };
+      groups.archisteamfarm = { };
     };
 
     systemd.services = {
-      asf = {
+      archisteamfarm = {
         description = "Archis-Steam-Farm Service";
         after = [ "network.target" ];
         wantedBy = [ "multi-user.target" ];
 
-        serviceConfig = mkMerge [
-          (mkIf (cfg.dataDir == "/var/lib/asf") {
-            StateDirectory = "asf";
+        serviceConfig = lib.mkMerge [
+          (lib.mkIf (lib.hasPrefix "/var/lib/" cfg.dataDir) {
+            StateDirectory = lib.last (lib.splitString "/" cfg.dataDir);
             StateDirectoryMode = "700";
           })
           {
-            User = "asf";
-            Group = "asf";
+            User = "archisteamfarm";
+            Group = "archisteamfarm";
             WorkingDirectory = cfg.dataDir;
             Type = "simple";
-            ExecStart = "${cfg.package}/bin/ArchiSteamFarm --path ${cfg.dataDir} --process-required --no-restart --service --no-config-migrate";
+            ExecStart = "${lib.getExe cfg.package} --no-restart --process-required --service --system-required --path ${cfg.dataDir}";
             Restart = "always";
 
-            # mostly copied from the default systemd service
-            PrivateTmp = true;
+            # copied from the default systemd service at
+            # https://github.com/JustArchiNET/ArchiSteamFarm/blob/main/ArchiSteamFarm/overlay/variant-base/linux/ArchiSteamFarm%40.service
+            CapabilityBoundingSet = "";
+            DevicePolicy = "closed";
             LockPersonality = true;
+            NoNewPrivileges = true;
             PrivateDevices = true;
             PrivateIPC = true;
             PrivateMounts = true;
+            PrivateTmp = true; # instead of rw /tmp
             PrivateUsers = true;
+            ProcSubset = "pid";
             ProtectClock = true;
             ProtectControlGroups = true;
+            ProtectHome = true;
             ProtectHostname = true;
             ProtectKernelLogs = true;
             ProtectKernelModules = true;
             ProtectKernelTunables = true;
             ProtectProc = "invisible";
-            ProtectSystem = "full";
+            ProtectSystem = "strict";
             RemoveIPC = true;
-            RestrictAddressFamilies = "AF_INET AF_INET6";
+            RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK AF_UNIX";
             RestrictNamespaces = true;
             RestrictRealtime = true;
             RestrictSUIDSGID = true;
+            SecureBits = "noroot-locked";
+            SystemCallArchitectures = "native";
+            SystemCallFilter = [ "@system-service" "~@privileged" ];
+            UMask = "0077";
           }
         ];
 
@@ -219,7 +230,7 @@ in
               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=",";
+              for i in ${lib.concatStringsSep " " (map (x: "${lib.getName x},${x}") (lib.mapAttrsToList mkBot cfg.bots))}; do IFS=",";
                 set -- $i
                 ln -fs $2 $out/$1
               done
@@ -229,22 +240,22 @@ in
           ''
             mkdir -p config
 
-            cp --no-preserve=mode ${asf-config} config/ASF.json
+            cp --no-preserve=mode ${configFile} config/ASF.json
 
-            ${optionalString (cfg.ipcPasswordFile != null) ''
+            ${lib.optionalString (cfg.ipcPasswordFile != null) ''
               ${replaceSecretBin} '#ipcPassword#' '${cfg.ipcPasswordFile}' config/ASF.json
             ''}
 
-            ${optionalString (cfg.ipcSettings != {}) ''
+            ${lib.optionalString (cfg.ipcSettings != {}) ''
               ln -fs ${ipc-config} config/IPC.config
             ''}
 
-            ${optionalString (cfg.ipcSettings != {}) ''
+            ${lib.optionalString (cfg.ipcSettings != {}) ''
               ln -fs ${createBotsScript}/* config/
             ''}
 
             rm -f www
-            ${optionalString cfg.web-ui.enable ''
+            ${lib.optionalString cfg.web-ui.enable ''
               ln -s ${cfg.web-ui.package}/ www
             ''}
           '';
@@ -254,6 +265,6 @@ in
 
   meta = {
     buildDocsInSandbox = false;
-    maintainers = with maintainers; [ lom SuperSandro2000 ];
+    maintainers = with lib.maintainers; [ lom SuperSandro2000 ];
   };
 }
diff --git a/nixos/modules/services/games/crossfire-server.nix b/nixos/modules/services/games/crossfire-server.nix
index 0849667e61c9f..b19a86253cb43 100644
--- a/nixos/modules/services/games/crossfire-server.nix
+++ b/nixos/modules/services/games/crossfire-server.nix
@@ -15,13 +15,11 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.crossfire-server;
-      defaultText = literalExpression "pkgs.crossfire-server";
-      description = lib.mdDoc ''
-        The package to use for the Crossfire server (and map/arch data, if you
-        don't change dataDir).
+    package = mkPackageOption pkgs "crossfire-server" {
+      extraDescription = ''
+        ::: {.note}
+        This will also be used for map/arch data, if you don't change {option}`dataDir`
+        :::
       '';
     };
 
diff --git a/nixos/modules/services/games/deliantra-server.nix b/nixos/modules/services/games/deliantra-server.nix
index f39044eda7c70..b405f338fe3d7 100644
--- a/nixos/modules/services/games/deliantra-server.nix
+++ b/nixos/modules/services/games/deliantra-server.nix
@@ -15,13 +15,11 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.deliantra-server;
-      defaultText = literalExpression "pkgs.deliantra-server";
-      description = lib.mdDoc ''
-        The package to use for the Deliantra server (and map/arch data, if you
-        don't change dataDir).
+    package = mkPackageOption pkgs "deliantra-server" {
+      extraDescription = ''
+        ::: {.note}
+        This will also be used for map/arch data, if you don't change {option}`dataDir`
+        :::
       '';
     };
 
diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix
index b349ffa2375f7..14bb80c2d1124 100644
--- a/nixos/modules/services/games/factorio.nix
+++ b/nixos/modules/services/games/factorio.nix
@@ -37,7 +37,8 @@ let
     autosave_only_on_server = true;
     non_blocking_saving = cfg.nonBlockingSaving;
   } // cfg.extraSettings;
-  serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings));
+  serverSettingsString = builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings);
+  serverSettingsFile = pkgs.writeText "server-settings.json" serverSettingsString;
   serverAdminsFile = pkgs.writeText "server-adminlist.json" (builtins.toJSON cfg.admins);
   modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods cfg.mods-dat;
 in
@@ -115,6 +116,23 @@ in
           customizations.
         '';
       };
+      extraSettingsFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc ''
+          File, which is dynamically applied to server-settings.json before
+          startup.
+
+          This option should be used for credentials.
+
+          For example a settings file could contain:
+          ```json
+          {
+            "game-password": "hunter1"
+          }
+          ```
+        '';
+      };
       stateDirName = mkOption {
         type = types.str;
         default = "factorio";
@@ -186,22 +204,20 @@ in
         default = null;
         description = lib.mdDoc ''
           Your factorio.com login credentials. Required for games with visibility public.
+
+          This option is insecure. Use extraSettingsFile instead.
         '';
       };
-      package = mkOption {
-        type = types.package;
-        default = pkgs.factorio-headless;
-        defaultText = literalExpression "pkgs.factorio-headless";
-        example = literalExpression "pkgs.factorio-headless-experimental";
-        description = lib.mdDoc ''
-          Factorio version to use. This defaults to the stable channel.
-        '';
+      package = mkPackageOption pkgs "factorio-headless" {
+        example = "factorio-headless-experimental";
       };
       password = mkOption {
         type = types.nullOr types.str;
         default = null;
         description = lib.mdDoc ''
           Your factorio.com login credentials. Required for games with visibility public.
+
+          This option is insecure. Use extraSettingsFile instead.
         '';
       };
       token = mkOption {
@@ -216,6 +232,8 @@ in
         default = null;
         description = lib.mdDoc ''
           Game password.
+
+          This option is insecure. Use extraSettingsFile instead.
         '';
       };
       requireUserVerification = mkOption {
@@ -251,14 +269,18 @@ in
       wantedBy      = [ "multi-user.target" ];
       after         = [ "network.target" ];
 
-      preStart = toString [
-        "test -e ${stateDir}/saves/${cfg.saveName}.zip"
-        "||"
-        "${cfg.package}/bin/factorio"
+      preStart =
+        (toString [
+          "test -e ${stateDir}/saves/${cfg.saveName}.zip"
+          "||"
+          "${cfg.package}/bin/factorio"
           "--config=${cfg.configFile}"
           "--create=${mkSavePath cfg.saveName}"
           (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
-      ];
+        ])
+        + (optionalString (cfg.extraSettingsFile != null) ("\necho ${lib.strings.escapeShellArg serverSettingsString}"
+          + " \"$(cat ${cfg.extraSettingsFile})\" | ${lib.getExe pkgs.jq} -s add"
+          + " > ${stateDir}/server-settings.json"));
 
       serviceConfig = {
         Restart = "always";
@@ -272,7 +294,11 @@ in
           "--port=${toString cfg.port}"
           "--bind=${cfg.bind}"
           (optionalString (!cfg.loadLatestSave) "--start-server=${mkSavePath cfg.saveName}")
-          "--server-settings=${serverSettingsFile}"
+          "--server-settings=${
+            if (cfg.extraSettingsFile != null)
+            then "${stateDir}/server-settings.json"
+            else 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/mchprs.nix b/nixos/modules/services/games/mchprs.nix
index a65001b0b3e28..71e546049c58b 100644
--- a/nixos/modules/services/games/mchprs.nix
+++ b/nixos/modules/services/games/mchprs.nix
@@ -73,12 +73,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mchprs;
-        defaultText = literalExpression "pkgs.mchprs";
-        description = mdDoc "Version of MCHPRS to run.";
-      };
+      package = mkPackageOption pkgs "mchprs" { };
 
       settings = mkOption {
         type = types.submodule {
diff --git a/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index 77f92ab97db74..116fc533dfd83 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -150,12 +150,8 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.minecraft-server;
-        defaultText = literalExpression "pkgs.minecraft-server";
-        example = literalExpression "pkgs.minecraft-server_1_12_2";
-        description = lib.mdDoc "Version of minecraft-server to run.";
+      package = mkPackageOption pkgs "minecraft-server" {
+        example = "minecraft-server_1_12_2";
       };
 
       jvmOpts = mkOption {
diff --git a/nixos/modules/services/games/openarena.nix b/nixos/modules/services/games/openarena.nix
index 8f6d4986903f6..14e485b06a0df 100644
--- a/nixos/modules/services/games/openarena.nix
+++ b/nixos/modules/services/games/openarena.nix
@@ -8,7 +8,7 @@ in
   options = {
     services.openarena = {
       enable = mkEnableOption (lib.mdDoc "OpenArena");
-      package = lib.mkPackageOptionMD pkgs "openarena" { };
+      package = lib.mkPackageOption pkgs "openarena" { };
 
       openPorts = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/games/quake3-server.nix b/nixos/modules/services/games/quake3-server.nix
index e51830c12e788..41688d56173ba 100644
--- a/nixos/modules/services/games/quake3-server.nix
+++ b/nixos/modules/services/games/quake3-server.nix
@@ -41,7 +41,7 @@ in {
   options = {
     services.quake3-server = {
       enable = mkEnableOption (lib.mdDoc "Quake 3 dedicated server");
-      package = lib.mkPackageOptionMD pkgs "ioquake3" { };
+      package = lib.mkPackageOption pkgs "ioquake3" { };
 
       port = mkOption {
         type = types.port;
diff --git a/nixos/modules/services/games/teeworlds.nix b/nixos/modules/services/games/teeworlds.nix
index ffef440330c4e..bd0df1ffca578 100644
--- a/nixos/modules/services/games/teeworlds.nix
+++ b/nixos/modules/services/games/teeworlds.nix
@@ -100,7 +100,7 @@ in
 
       serviceConfig = {
         DynamicUser = true;
-        ExecStart = "${pkgs.teeworlds}/bin/teeworlds_srv -f ${teeworldsConf}";
+        ExecStart = "${pkgs.teeworlds-server}/bin/teeworlds_srv -f ${teeworldsConf}";
 
         # Hardening
         CapabilityBoundingSet = false;
diff --git a/nixos/modules/services/games/xonotic.nix b/nixos/modules/services/games/xonotic.nix
new file mode 100644
index 0000000000000..c84347ddc9813
--- /dev/null
+++ b/nixos/modules/services/games/xonotic.nix
@@ -0,0 +1,198 @@
+{ config
+, pkgs
+, lib
+, ...
+}:
+
+let
+  cfg = config.services.xonotic;
+
+  serverCfg = pkgs.writeText "xonotic-server.cfg" (
+    toString cfg.prependConfig
+      + "\n"
+      + builtins.concatStringsSep "\n" (
+        lib.mapAttrsToList (key: option:
+          let
+            escape = s: lib.escape [ "\"" ] s;
+            quote = s: "\"${s}\"";
+
+            toValue = x: quote (escape (toString x));
+
+            value = (if lib.isList option then
+              builtins.concatStringsSep
+                " "
+                (builtins.map (x: toValue x) option)
+            else
+              toValue option
+            );
+          in
+          "${key} ${value}"
+        ) cfg.settings
+      )
+      + "\n"
+      + toString cfg.appendConfig
+  );
+in
+
+{
+  options.services.xonotic = {
+    enable = lib.mkEnableOption (lib.mdDoc "Xonotic dedicated server");
+
+    package = lib.mkPackageOption pkgs "xonotic-dedicated" {};
+
+    openFirewall = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Open the firewall for TCP and UDP on the specified port.
+      '';
+    };
+
+    dataDir = lib.mkOption {
+      type = lib.types.path;
+      readOnly = true;
+      default = "/var/lib/xonotic";
+      description = lib.mdDoc ''
+        Data directory.
+      '';
+    };
+
+    settings = lib.mkOption {
+      description = lib.mdDoc ''
+        Generates the `server.cfg` file. Refer to [upstream's example][0] for
+        details.
+
+        [0]: https://gitlab.com/xonotic/xonotic/-/blob/master/server/server.cfg
+      '';
+      default = {};
+      type = lib.types.submodule {
+        freeformType = with lib.types; let
+          scalars = oneOf [ singleLineStr int float ];
+        in
+        attrsOf (oneOf [ scalars (nonEmptyListOf scalars) ]);
+
+        options.sv_public = lib.mkOption {
+          type = lib.types.int;
+          default = 0;
+          example = [ (-1) 1 ];
+          description = lib.mdDoc ''
+            Controls whether the server will be publicly listed.
+          '';
+        };
+
+        options.hostname = lib.mkOption {
+          type = lib.types.singleLineStr;
+          default = "Xonotic $g_xonoticversion Server";
+          description = lib.mdDoc ''
+            The name that will appear in the server list. `$g_xonoticversion`
+            gets replaced with the current version.
+          '';
+        };
+
+        options.sv_motd = lib.mkOption {
+          type = lib.types.singleLineStr;
+          default = "";
+          description = lib.mdDoc ''
+            Text displayed when players join the server.
+          '';
+        };
+
+        options.sv_termsofservice_url = lib.mkOption {
+          type = lib.types.singleLineStr;
+          default = "";
+          description = lib.mdDoc ''
+            URL for the Terms of Service for playing on your server.
+          '';
+        };
+
+        options.maxplayers = lib.mkOption {
+          type = lib.types.int;
+          default = 16;
+          description = lib.mdDoc ''
+            Number of player slots on the server, including spectators.
+          '';
+        };
+
+        options.net_address = lib.mkOption {
+          type = lib.types.singleLineStr;
+          default = "0.0.0.0";
+          description = lib.mdDoc ''
+            The address Xonotic will listen on.
+          '';
+        };
+
+        options.port = lib.mkOption {
+          type = lib.types.port;
+          default = 26000;
+          description = lib.mdDoc ''
+            The port Xonotic will listen on.
+          '';
+        };
+      };
+    };
+
+    # Still useful even though we're using RFC 42 settings because *some* keys
+    # can be repeated.
+    appendConfig = lib.mkOption {
+      type = with lib.types; nullOr lines;
+      default = null;
+      description = lib.mdDoc ''
+        Literal text to insert at the end of `server.cfg`.
+      '';
+    };
+
+    # Certain changes need to happen at the beginning of the file.
+    prependConfig = lib.mkOption {
+      type = with lib.types; nullOr lines;
+      default = null;
+      description = lib.mdDoc ''
+        Literal text to insert at the start of `server.cfg`.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.xonotic = {
+      description = "Xonotic server";
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        # Required or else it tries to write the lock file into the nix store
+        HOME = cfg.dataDir;
+      };
+
+      serviceConfig = {
+        DynamicUser = true;
+        User = "xonotic";
+        StateDirectory = "xonotic";
+        ExecStart = "${cfg.package}/bin/xonotic-dedicated";
+
+        # Symlink the configuration from the nix store to where Xonotic actually
+        # looks for it
+        ExecStartPre = [
+          "${pkgs.coreutils}/bin/mkdir -p ${cfg.dataDir}/.xonotic/data"
+          ''
+            ${pkgs.coreutils}/bin/ln -sf ${serverCfg} \
+              ${cfg.dataDir}/.xonotic/data/server.cfg
+          ''
+        ];
+
+        # Cargo-culted from search results about writing Xonotic systemd units
+        ExecReload = "${pkgs.util-linux}/bin/kill -HUP $MAINPID";
+
+        Restart = "on-failure";
+        RestartSec = 10;
+        StartLimitBurst = 5;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
+      cfg.settings.port
+    ];
+    networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [
+      cfg.settings.port
+    ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ CobaltCause ];
+}
diff --git a/nixos/modules/services/hardware/acpid.nix b/nixos/modules/services/hardware/acpid.nix
index 821f4ef205fc5..6021aad09f450 100644
--- a/nixos/modules/services/hardware/acpid.nix
+++ b/nixos/modules/services/hardware/acpid.nix
@@ -135,6 +135,7 @@ in
       wantedBy = [ "multi-user.target" ];
 
       serviceConfig = {
+        PrivateNetwork = true;
         ExecStart = escapeShellArgs
           ([ "${pkgs.acpid}/bin/acpid"
              "--foreground"
diff --git a/nixos/modules/services/hardware/auto-cpufreq.nix b/nixos/modules/services/hardware/auto-cpufreq.nix
index fd2e03ef12f5d..9c69ba8920f35 100644
--- a/nixos/modules/services/hardware/auto-cpufreq.nix
+++ b/nixos/modules/services/hardware/auto-cpufreq.nix
@@ -15,8 +15,7 @@ in {
         description = lib.mdDoc ''
           Configuration for `auto-cpufreq`.
 
-          See its [example configuration file] for supported settings.
-          [example configuration file]: https://github.com/AdnanHodzic/auto-cpufreq/blob/master/auto-cpufreq.conf-example
+          The available options can be found in [the example configuration file](https://github.com/AdnanHodzic/auto-cpufreq/blob/v${pkgs.auto-cpufreq.version}/auto-cpufreq.conf-example).
           '';
 
         default = {};
@@ -35,6 +34,7 @@ in {
         wantedBy = [ "multi-user.target" ];
         path = with pkgs; [ bash coreutils ];
 
+        serviceConfig.WorkingDirectory = "";
         serviceConfig.ExecStart = [
           ""
           "${lib.getExe pkgs.auto-cpufreq} --daemon --config ${cfgFile}"
@@ -42,4 +42,10 @@ in {
       };
     };
   };
+
+  # uses attributes of the linked package
+  meta = {
+    buildDocsInSandbox = false;
+    maintainers = with lib.maintainers; [ nicoo ];
+  };
 }
diff --git a/nixos/modules/services/hardware/auto-epp.nix b/nixos/modules/services/hardware/auto-epp.nix
new file mode 100644
index 0000000000000..84b6a337d28a2
--- /dev/null
+++ b/nixos/modules/services/hardware/auto-epp.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.auto-epp;
+  format = pkgs.formats.ini {};
+
+  inherit (lib) mkOption types;
+in {
+  options = {
+    services.auto-epp = {
+      enable = lib.mkEnableOption (lib.mdDoc "auto-epp for amd active pstate");
+
+      package = lib.mkPackageOptionMD pkgs "auto-epp" {};
+
+      settings = mkOption {
+        type = types.submodule {
+          freeformType = format.type;
+          options = {
+            Settings = {
+              epp_state_for_AC = mkOption {
+                type = types.str;
+                default = "balance_performance";
+                description = lib.mdDoc ''
+                  energy_performance_preference when on plugged in
+
+                  ::: {.note}
+                  See available epp states by running:
+                  {command}`cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences`
+                  :::
+                '';
+              };
+
+              epp_state_for_BAT = mkOption {
+                type = types.str;
+                default = "power";
+                description = lib.mdDoc ''
+                  `energy_performance_preference` when on battery
+
+                  ::: {.note}
+                  See available epp states by running:
+                  {command}`cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_available_preferences`
+                  :::
+                '';
+              };
+            };
+          };
+        };
+        default = {};
+        description = lib.mdDoc ''
+          Settings for the auto-epp application.
+          See upstream example: <https://github.com/jothi-prasath/auto-epp/blob/master/sample-auto-epp.conf>
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    boot.kernelParams = [
+      "amd_pstate=active"
+    ];
+
+    environment.etc."auto-epp.conf".source = format.generate "auto-epp.conf" cfg.settings;
+    systemd.packages = [ cfg.package ];
+
+    systemd.services.auto-epp = {
+      after = [ "multi-user.target" ];
+      wantedBy  = [ "multi-user.target" ];
+      description = "auto-epp - Automatic EPP Changer for amd-pstate-epp";
+      serviceConfig = {
+        Type = "simple";
+        User = "root";
+        ExecStart = lib.getExe cfg.package;
+        Restart = "on-failure";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ lamarios ];
+}
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
index 2a58be51bb023..51ec12f96537e 100644
--- a/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -4,7 +4,7 @@ let
   package = cfg.package;
 
   inherit (lib)
-    mkDefault mkEnableOption mkIf mkOption
+    mkDefault mkEnableOption mkIf mkOption mkPackageOption
     mkRenamedOptionModule mkRemovedOptionModule
     concatStringsSep escapeShellArgs literalExpression
     optional optionals optionalAttrs recursiveUpdate types;
@@ -46,14 +46,7 @@ in
         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";
-        description = lib.mdDoc ''
-          Which BlueZ package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "bluez" { };
 
       disabledPlugins = mkOption {
         type = types.listOf types.str;
diff --git a/nixos/modules/services/hardware/freefall.nix b/nixos/modules/services/hardware/freefall.nix
index 7b794264ff35e..2985739bc2df0 100644
--- a/nixos/modules/services/hardware/freefall.nix
+++ b/nixos/modules/services/hardware/freefall.nix
@@ -18,14 +18,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.freefall;
-      defaultText = literalExpression "pkgs.freefall";
-      description = lib.mdDoc ''
-        freefall derivation to use.
-      '';
-    };
+    package = mkPackageOption pkgs "freefall" { };
 
     devices = mkOption {
       type = types.listOf types.str;
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index 4e5913fd27515..6b3a109ed6f7f 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -94,14 +94,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.fwupd;
-        defaultText = literalExpression "pkgs.fwupd";
-        description = lib.mdDoc ''
-          Which fwupd package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "fwupd" { };
 
       daemonSettings = mkOption {
         type = types.submodule {
@@ -181,7 +174,25 @@ in {
     # required to update the firmware of disks
     services.udisks2.enable = true;
 
-    systemd.packages = [ cfg.package ];
+    systemd = {
+      packages = [ cfg.package ];
+
+      # fwupd-refresh expects a user that we do not create, so just run with DynamicUser
+      # instead and ensure we take ownership of /var/lib/fwupd
+      services.fwupd-refresh.serviceConfig = {
+        StateDirectory = "fwupd";
+        # Better for debugging, upstream sets stderr to null for some reason..
+        StandardError = "inherit";
+      };
+
+      timers.fwupd-refresh.wantedBy = [ "timers.target" ];
+    };
+
+    users.users.fwupd-refresh = {
+      isSystemUser = true;
+      group = "fwupd-refresh";
+    };
+    users.groups.fwupd-refresh = {};
 
     security.polkit.enable = true;
   };
diff --git a/nixos/modules/services/hardware/iptsd.nix b/nixos/modules/services/hardware/iptsd.nix
new file mode 100644
index 0000000000000..8af0a6d6bbe1f
--- /dev/null
+++ b/nixos/modules/services/hardware/iptsd.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.iptsd;
+  format = pkgs.formats.ini { };
+  configFile = format.generate "iptsd.conf" cfg.config;
+in {
+  options.services.iptsd = {
+    enable = lib.mkEnableOption (lib.mdDoc "the userspace daemon for Intel Precise Touch & Stylus");
+
+    config = lib.mkOption {
+      default = { };
+      description = lib.mdDoc ''
+        Configuration for IPTSD. See the
+        [reference configuration](https://github.com/linux-surface/iptsd/blob/master/etc/iptsd.conf)
+        for available options and defaults.
+      '';
+      type = lib.types.submodule {
+        freeformType = format.type;
+        options = {
+          Touch = {
+            DisableOnPalm = lib.mkOption {
+              default = false;
+              description = lib.mdDoc "Ignore all touch inputs if a palm was registered on the display.";
+              type = lib.types.bool;
+            };
+            DisableOnStylus = lib.mkOption {
+              default = false;
+              description = lib.mdDoc "Ignore all touch inputs if a stylus is in proximity.";
+              type = lib.types.bool;
+            };
+          };
+          Stylus = {
+            Disable = lib.mkOption {
+              default = false;
+              description = lib.mdDoc "Disables the stylus. No stylus data will be processed.";
+              type = lib.types.bool;
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [ pkgs.iptsd ];
+    environment.etc."iptsd.conf".source = configFile;
+    systemd.services."iptsd@".restartTriggers = [ configFile ];
+    services.udev.packages = [ pkgs.iptsd ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ dotlambda ];
+}
diff --git a/nixos/modules/services/hardware/joycond.nix b/nixos/modules/services/hardware/joycond.nix
index df3239cb2a7df..060303b520e5c 100644
--- a/nixos/modules/services/hardware/joycond.nix
+++ b/nixos/modules/services/hardware/joycond.nix
@@ -10,14 +10,7 @@ with lib;
   options.services.joycond = {
     enable = mkEnableOption (lib.mdDoc "support for Nintendo Pro Controllers and Joycons");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.joycond;
-      defaultText = lib.literalExpression "pkgs.joycond";
-      description = lib.mdDoc ''
-        The joycond package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "joycond" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/hardware/kanata.nix b/nixos/modules/services/hardware/kanata.nix
index 7d544050130b9..05e76d8432154 100644
--- a/nixos/modules/services/hardware/kanata.nix
+++ b/nixos/modules/services/hardware/kanata.nix
@@ -9,8 +9,14 @@ let
     options = {
       devices = mkOption {
         type = types.listOf types.str;
+        default = [ ];
         example = [ "/dev/input/by-id/usb-0000_0000-event-kbd" ];
-        description = mdDoc "Paths to keyboard devices.";
+        description = mdDoc ''
+          Paths to keyboard devices.
+
+          An empty list, the default value, lets kanata detect which
+          input devices are keyboards and intercept them all.
+        '';
       };
       config = mkOption {
         type = types.lines;
@@ -72,7 +78,13 @@ let
   mkName = name: "kanata-${name}";
 
   mkDevices = devices:
-    optionalString ((length devices) > 0) "linux-dev ${concatStringsSep ":" devices}";
+    let
+      devicesString = pipe devices [
+        (map (device: "\"" + device + "\""))
+        (concatStringsSep " ")
+      ];
+    in
+    optionalString ((length devices) > 0) "linux-dev (${devicesString})";
 
   mkConfig = name: keyboard: pkgs.writeText "${mkName name}-config.kdb" ''
     (defcfg
@@ -140,16 +152,11 @@ in
 {
   options.services.kanata = {
     enable = mkEnableOption (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.
-
+    package = mkPackageOption pkgs "kanata" {
+      example = "kanata-with-cmd";
+      extraDescription = ''
         ::: {.note}
-        If `danger-enable-cmd` is enabled in any of the keyboards, the
+        If {option}`danger-enable-cmd` is enabled in any of the keyboards, the
         `kanata-with-cmd` package should be used.
         :::
       '';
@@ -162,6 +169,14 @@ in
   };
 
   config = mkIf cfg.enable {
+    warnings =
+      let
+        keyboardsWithEmptyDevices = filterAttrs (name: keyboard: keyboard.devices == [ ]) cfg.keyboards;
+        existEmptyDevices = length (attrNames keyboardsWithEmptyDevices) > 0;
+        moreThanOneKeyboard = length (attrNames cfg.keyboards) > 1;
+      in
+      optional (existEmptyDevices && moreThanOneKeyboard) "One device can only be intercepted by one kanata instance.  Setting services.kanata.keyboards.${head (attrNames keyboardsWithEmptyDevices)}.devices = [ ] and using more than one services.kanata.keyboards may cause a race condition.";
+
     hardware.uinput.enable = true;
 
     systemd.services = mapAttrs' mkService cfg.keyboards;
diff --git a/nixos/modules/services/hardware/keyd.nix b/nixos/modules/services/hardware/keyd.nix
index ead2f456a2024..77297401a51c7 100644
--- a/nixos/modules/services/hardware/keyd.nix
+++ b/nixos/modules/services/hardware/keyd.nix
@@ -2,7 +2,6 @@
 with lib;
 let
   cfg = config.services.keyd;
-  settingsFormat = pkgs.formats.ini { };
 
   keyboardOptions = { ... }: {
     options = {
@@ -16,7 +15,7 @@ let
       };
 
       settings = mkOption {
-        type = settingsFormat.type;
+        type = (pkgs.formats.ini { }).type;
         default = { };
         example = {
           main = {
@@ -37,6 +36,20 @@ let
           See <https://github.com/rvaiya/keyd> how to configure.
         '';
       };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          [control+shift]
+          h = left
+        '';
+        description = lib.mdDoc ''
+          Extra configuration that is appended to the end of the file.
+          **Do not** write `ids` section here, use a separate option for it.
+          You can use this option to define compound layers that must always be defined after the layer they are comprised.
+        '';
+      };
     };
   };
 in
@@ -85,15 +98,12 @@ in
     environment.etc = mapAttrs'
       (name: options:
         nameValuePair "keyd/${name}.conf" {
-          source = pkgs.runCommand "${name}.conf"
-            {
-              ids = ''
-                [ids]
-                ${concatStringsSep "\n" options.ids}
-              '';
-              passAsFile = [ "ids" ];
-            } ''
-            cat $idsPath <(echo) ${settingsFormat.generate "keyd-${name}.conf" options.settings} >$out
+          text = ''
+            [ids]
+            ${concatStringsSep "\n" options.ids}
+
+            ${generators.toINI {} options.settings}
+            ${options.extraConfig}
           '';
         })
       cfg.keyboards;
@@ -133,7 +143,7 @@ in
         RuntimeDirectory = "keyd";
 
         # Hardening
-        CapabilityBoundingSet = "";
+        CapabilityBoundingSet = [ "CAP_SYS_NICE" ];
         DeviceAllow = [
           "char-input rw"
           "/dev/uinput rw"
@@ -142,7 +152,7 @@ in
         PrivateNetwork = true;
         ProtectHome = true;
         ProtectHostname = true;
-        PrivateUsers = true;
+        PrivateUsers = false;
         PrivateMounts = true;
         PrivateTmp = true;
         RestrictNamespaces = true;
@@ -155,9 +165,9 @@ in
         LockPersonality = true;
         ProtectProc = "invisible";
         SystemCallFilter = [
+          "nice"
           "@system-service"
           "~@privileged"
-          "~@resources"
         ];
         RestrictAddressFamilies = [ "AF_UNIX" ];
         RestrictSUIDSGID = true;
diff --git a/nixos/modules/services/hardware/openrgb.nix b/nixos/modules/services/hardware/openrgb.nix
index 310615ecc5396..81b199e50778e 100644
--- a/nixos/modules/services/hardware/openrgb.nix
+++ b/nixos/modules/services/hardware/openrgb.nix
@@ -8,16 +8,18 @@ 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.";
-    };
+    package = mkPackageOption pkgs "openrgb" { };
 
     motherboard = mkOption {
       type = types.nullOr (types.enum [ "amd" "intel" ]);
-      default = null;
+      default = if config.hardware.cpu.intel.updateMicrocode then "intel"
+        else if config.hardware.cpu.amd.updateMicrocode then "amd"
+        else null;
+      defaultText = literalMD ''
+        if config.hardware.cpu.intel.updateMicrocode then "intel"
+        else if config.hardware.cpu.amd.updateMicrocode then "amd"
+        else null;
+      '';
       description = lib.mdDoc "CPU family of motherboard. Allows for addition motherboard i2c support.";
     };
 
diff --git a/nixos/modules/services/hardware/pcscd.nix b/nixos/modules/services/hardware/pcscd.nix
index a9e4998efe37a..85accd8335f78 100644
--- a/nixos/modules/services/hardware/pcscd.nix
+++ b/nixos/modules/services/hardware/pcscd.nix
@@ -16,9 +16,6 @@ let
 
 in
 {
-
-  ###### interface
-
   options.services.pcscd = {
     enable = mkEnableOption (lib.mdDoc "PCSC-Lite daemon");
 
@@ -46,13 +43,10 @@ in
     };
   };
 
-  ###### implementation
-
   config = mkIf config.services.pcscd.enable {
-
     environment.etc."reader.conf".source = cfgFile;
 
-    environment.systemPackages = [ package ];
+    environment.systemPackages = [ package.out ];
     systemd.packages = [ (getBin package) ];
 
     services.pcscd.plugins = [ pkgs.ccid ];
@@ -61,7 +55,6 @@ in
 
     systemd.services.pcscd = {
       environment.PCSCLITE_HP_DROPDIR = pluginEnv;
-      restartTriggers = [ "/etc/reader.conf" ];
 
       # If the cfgFile is empty and not specified (in which case the default
       # /etc/reader.conf is assumed), pcscd will happily start going through the
diff --git a/nixos/modules/services/hardware/power-profiles-daemon.nix b/nixos/modules/services/hardware/power-profiles-daemon.nix
index 101da01b4a712..1d84bf8ac937c 100644
--- a/nixos/modules/services/hardware/power-profiles-daemon.nix
+++ b/nixos/modules/services/hardware/power-profiles-daemon.nix
@@ -1,10 +1,7 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.services.power-profiles-daemon;
-  package = pkgs.power-profiles-daemon;
 in
 
 {
@@ -15,8 +12,8 @@ in
 
     services.power-profiles-daemon = {
 
-      enable = mkOption {
-        type = types.bool;
+      enable = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc ''
           Whether to enable power-profiles-daemon, a DBus daemon that allows
@@ -24,6 +21,8 @@ in
         '';
       };
 
+      package = lib.mkPackageOption pkgs "power-profiles-daemon" { };
+
     };
 
   };
@@ -31,7 +30,7 @@ in
 
   ###### implementation
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
 
     assertions = [
       { assertion = !config.services.tlp.enable;
@@ -42,13 +41,13 @@ in
       }
     ];
 
-    environment.systemPackages = [ package ];
+    environment.systemPackages = [ cfg.package ];
 
-    services.dbus.packages = [ package ];
+    services.dbus.packages = [ cfg.package ];
 
-    services.udev.packages = [ package ];
+    services.udev.packages = [ cfg.package ];
 
-    systemd.packages = [ package ];
+    systemd.packages = [ cfg.package ];
 
   };
 
diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix
index 2cac2e8e8bb47..8f64afe60734c 100644
--- a/nixos/modules/services/hardware/sane.nix
+++ b/nixos/modules/services/hardware/sane.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
 
-  pkg = pkgs.sane-backends.override {
+  pkg = config.hardware.sane.backends-package.override {
     scanSnapDriversUnfree = config.hardware.sane.drivers.scanSnap.enable;
     scanSnapDriversPackage = config.hardware.sane.drivers.scanSnap.package;
   };
@@ -57,6 +57,13 @@ in
       '';
     };
 
+    hardware.sane.backends-package = mkOption {
+      type = types.package;
+      default = pkgs.sane-backends;
+      defaultText = literalExpression "pkgs.sane-backends";
+      description = lib.mdDoc "Backends driver package to use.";
+    };
+
     hardware.sane.snapshot = mkOption {
       type = types.bool;
       default = false;
@@ -114,14 +121,11 @@ in
       '';
     };
 
-    hardware.sane.drivers.scanSnap.package = mkOption {
-      type = types.package;
-      default = pkgs.sane-drivers.epjitsu;
-      defaultText = literalExpression "pkgs.sane-drivers.epjitsu";
-      description = lib.mdDoc ''
-        Epjitsu driver package to use. Useful if you want to extract the driver files yourself.
+    hardware.sane.drivers.scanSnap.package = mkPackageOption pkgs [ "sane-drivers" "epjitsu" ] {
+      extraDescription = ''
+        Useful if you want to extract the driver files yourself.
 
-        The process is described in the `/etc/sane.d/epjitsu.conf` file in
+        The process is described in the {file}`/etc/sane.d/epjitsu.conf` file in
         the `sane-backends` package.
       '';
     };
diff --git a/nixos/modules/services/hardware/supergfxd.nix b/nixos/modules/services/hardware/supergfxd.nix
index bd82775e82461..f7af993d7238c 100644
--- a/nixos/modules/services/hardware/supergfxd.nix
+++ b/nixos/modules/services/hardware/supergfxd.nix
@@ -7,7 +7,7 @@ in
 {
   options = {
     services.supergfxd = {
-      enable = lib.mkEnableOption (lib.mdDoc "Enable the supergfxd service");
+      enable = lib.mkEnableOption (lib.mdDoc "the supergfxd service");
 
       settings = lib.mkOption {
         type = lib.types.nullOr json.type;
diff --git a/nixos/modules/services/hardware/thermald.nix b/nixos/modules/services/hardware/thermald.nix
index 6b694ede58856..a4839f326cc45 100644
--- a/nixos/modules/services/hardware/thermald.nix
+++ b/nixos/modules/services/hardware/thermald.nix
@@ -19,18 +19,19 @@ in
         '';
       };
 
+     ignoreCpuidCheck = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Whether to ignore the cpuid check to allow running on unsupported platforms";
+      };
+
       configFile = mkOption {
         type = types.nullOr types.path;
         default = null;
         description = lib.mdDoc "the thermald manual configuration file.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.thermald;
-        defaultText = literalExpression "pkgs.thermald";
-        description = lib.mdDoc "Which thermald package to use.";
-      };
+      package = mkPackageOption pkgs "thermald" { };
     };
   };
 
@@ -47,6 +48,7 @@ in
           ${cfg.package}/sbin/thermald \
             --no-daemon \
             ${optionalString cfg.debug "--loglevel=debug"} \
+            ${optionalString cfg.ignoreCpuidCheck "--ignore-cpuid-check"} \
             ${optionalString (cfg.configFile != null) "--config-file ${cfg.configFile}"} \
             --dbus-enable \
             --adaptive
diff --git a/nixos/modules/services/hardware/thinkfan.nix b/nixos/modules/services/hardware/thinkfan.nix
index 8fa7b456f20e2..cca35f492b8e3 100644
--- a/nixos/modules/services/hardware/thinkfan.nix
+++ b/nixos/modules/services/hardware/thinkfan.nix
@@ -217,6 +217,8 @@ in {
 
     systemd.services = {
       thinkfan.environment.THINKFAN_ARGS = escapeShellArgs ([ "-c" configFile ] ++ cfg.extraArgs);
+      thinkfan.serviceConfig.Restart = "on-failure";
+      thinkfan.serviceConfig.RestartSec = "30s";
 
       # must be added manually, see issue #81138
       thinkfan.wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
index afca24d976e1a..0f1f00348ee8d 100644
--- a/nixos/modules/services/hardware/throttled.nix
+++ b/nixos/modules/services/hardware/throttled.nix
@@ -27,10 +27,10 @@ in {
       then pkgs.writeText "throttled.conf" cfg.extraConfig
       else "${pkgs.throttled}/etc/throttled.conf";
 
+    hardware.cpu.x86.msr.enable = true;
     # Kernel 5.9 spams warnings whenever userspace writes to CPU MSRs.
     # See https://github.com/erpalma/throttled/issues/215
-    boot.kernelParams =
-      optional (versionAtLeast config.boot.kernelPackages.kernel.version "5.9")
-      "msr.allow_writes=on";
+    hardware.cpu.x86.msr.settings.allow-writes =
+      mkIf (versionAtLeast config.boot.kernelPackages.kernel.version "5.9") "on";
   };
 }
diff --git a/nixos/modules/services/hardware/tlp.nix b/nixos/modules/services/hardware/tlp.nix
index d2cc7c661c693..0b7f98ab6a6da 100644
--- a/nixos/modules/services/hardware/tlp.nix
+++ b/nixos/modules/services/hardware/tlp.nix
@@ -47,7 +47,7 @@ in
 
   ###### implementation
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "msr" ];
+    hardware.cpu.x86.msr.enable = true;
 
     warnings = optional (cfg.extraConfig != "") ''
       Using config.services.tlp.extraConfig is deprecated and will become unsupported in a future release. Use config.services.tlp.settings instead.
@@ -65,7 +65,7 @@ in
       "tlp.conf".text = (mkTlpConfig cfg.settings) + cfg.extraConfig;
     } // optionalAttrs enableRDW {
       "NetworkManager/dispatcher.d/99tlp-rdw-nm".source =
-        "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm";
+        "${tlp}/usr/lib/NetworkManager/dispatcher.d/99tlp-rdw-nm";
     };
 
     environment.systemPackages = [ tlp ];
diff --git a/nixos/modules/services/hardware/tuxedo-rs.nix b/nixos/modules/services/hardware/tuxedo-rs.nix
new file mode 100644
index 0000000000000..0daccfef3a530
--- /dev/null
+++ b/nixos/modules/services/hardware/tuxedo-rs.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.tuxedo-rs;
+
+in
+{
+  options = {
+    hardware.tuxedo-rs = {
+      enable = mkEnableOption (lib.mdDoc "Rust utilities for interacting with hardware from TUXEDO Computers");
+
+      tailor-gui.enable = mkEnableOption (lib.mdDoc "tailor-gui, an alternative to TUXEDO Control Center, written in Rust");
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      hardware.tuxedo-keyboard.enable = true;
+
+      systemd = {
+        services.tailord = {
+          enable = true;
+          description = "Tuxedo Tailor hardware control service";
+          after = [ "systemd-logind.service" ];
+          wantedBy = [ "multi-user.target" ];
+
+          serviceConfig = {
+            Type = "dbus";
+            BusName = "com.tux.Tailor";
+            ExecStart = "${pkgs.tuxedo-rs}/bin/tailord";
+            Environment = "RUST_BACKTRACE=1";
+            Restart = "on-failure";
+          };
+        };
+      };
+
+      services.dbus.packages = [ pkgs.tuxedo-rs ];
+
+      environment.systemPackages = [ pkgs.tuxedo-rs ];
+    }
+    (mkIf cfg.tailor-gui.enable {
+      environment.systemPackages = [ pkgs.tailor-gui ];
+    })
+  ]);
+
+  meta.maintainers = with maintainers; [ mrcjkb ];
+}
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 56120094871cc..670b9087f1107 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -112,7 +112,8 @@ let
       echo "OK"
 
       filesToFixup="$(for i in "$out"/*; do
-        grep -l '\B\(/usr\)\?/s\?bin' "$i" || :
+        # list all files referring to (/usr)/bin paths, but allow references to /bin/sh.
+        grep -P -l '\B(?!\/bin\/sh\b)(\/usr)?\/bin(?:\/.*)?' "$i" || :
       done)"
 
       if [ -n "$filesToFixup" ]; then
@@ -222,6 +223,9 @@ in
         description = lib.mdDoc ''
           Packages added to the {env}`PATH` environment variable when
           executing programs from Udev rules.
+
+          coreutils, gnu{sed,grep}, util-linux and config.systemd.package are
+          automatically included.
         '';
       };
 
@@ -279,7 +283,7 @@ in
       default = true;
       type = types.bool;
       description = lib.mdDoc ''
-        Whether to assign [predictable names to network interfaces](http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames).
+        Whether to assign [predictable names to network interfaces](https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/).
         If enabled, interfaces
         are assigned names that contain topology information
         (e.g. `wlp3s0`) and thus should be stable
@@ -350,7 +354,7 @@ in
 
     boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
 
-    boot.initrd.extraUdevRulesCommands = optionalString (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
+    boot.initrd.extraUdevRulesCommands = mkIf (!config.boot.initrd.systemd.enable && config.boot.initrd.services.udev.rules != "")
       ''
         cat <<'EOF' > $out/99-local.rules
         ${config.boot.initrd.services.udev.rules}
diff --git a/nixos/modules/services/hardware/undervolt.nix b/nixos/modules/services/hardware/undervolt.nix
index 944777475401b..67d8171587bb2 100644
--- a/nixos/modules/services/hardware/undervolt.nix
+++ b/nixos/modules/services/hardware/undervolt.nix
@@ -47,14 +47,7 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.undervolt;
-      defaultText = literalExpression "pkgs.undervolt";
-      description = lib.mdDoc ''
-        undervolt derivation to use.
-      '';
-    };
+    package = mkPackageOption pkgs "undervolt" { };
 
     coreOffset = mkOption {
       type = types.nullOr types.int;
@@ -159,7 +152,7 @@ in
   };
 
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "msr" ];
+    hardware.cpu.x86.msr.enable = true;
 
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix
index aacc8a63dbeb7..0ae31d99aa86b 100644
--- a/nixos/modules/services/hardware/upower.nix
+++ b/nixos/modules/services/hardware/upower.nix
@@ -27,14 +27,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.upower;
-        defaultText = literalExpression "pkgs.upower";
-        description = lib.mdDoc ''
-          Which upower package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "upower" { };
 
       enableWattsUpPro = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/hardware/usbmuxd.nix b/nixos/modules/services/hardware/usbmuxd.nix
index 9466ea26995b8..d05ad3af8b127 100644
--- a/nixos/modules/services/hardware/usbmuxd.nix
+++ b/nixos/modules/services/hardware/usbmuxd.nix
@@ -77,7 +77,7 @@ in
       serviceConfig = {
         # 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}";
+        ExecStartPre = "${config.systemd.package}/bin/udevadm trigger -s usb -a idVendor=${apple}";
         ExecStart = "${cfg.package}/bin/usbmuxd -U ${cfg.user} -v";
       };
     };
diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix
index de63ed893b029..689d83f7eedcd 100644
--- a/nixos/modules/services/hardware/vdr.nix
+++ b/nixos/modules/services/hardware/vdr.nix
@@ -1,82 +1,101 @@
 { config, lib, pkgs, ... }:
-
-with lib;
-
 let
   cfg = config.services.vdr;
-  libDir = "/var/lib/vdr";
-in {
-
-  ###### interface
 
+  inherit (lib)
+    mkEnableOption mkPackageOption mkOption types mkIf optional mdDoc;
+in
+{
   options = {
 
     services.vdr = {
-      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 = lib.mdDoc "Package to use.";
+      enable = mkEnableOption (mdDoc "Start VDR");
+
+      package = mkPackageOption pkgs "vdr" {
+        example = "wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
       };
 
       videoDir = mkOption {
         type = types.path;
         default = "/srv/vdr/video";
-        description = lib.mdDoc "Recording directory";
+        description = mdDoc "Recording directory";
       };
 
       extraArguments = mkOption {
         type = types.listOf types.str;
-        default = [];
-        description = lib.mdDoc "Additional command line arguments to pass to VDR.";
+        default = [ ];
+        description = mdDoc "Additional command line arguments to pass to VDR.";
+      };
+
+      enableLirc = mkEnableOption (mdDoc "LIRC");
+
+      user = mkOption {
+        type = types.str;
+        default = "vdr";
+        description = mdDoc ''
+          User under which the VDR service runs.
+        '';
       };
 
-      enableLirc = mkEnableOption (lib.mdDoc "LIRC");
+      group = mkOption {
+        type = types.str;
+        default = "vdr";
+        description = mdDoc ''
+          Group under which the VDRvdr service runs.
+        '';
+      };
     };
+
   };
 
-  ###### implementation
+  config = mkIf cfg.enable {
 
-  config = mkIf cfg.enable (mkMerge [{
     systemd.tmpfiles.rules = [
-      "d ${cfg.videoDir} 0755 vdr vdr -"
-      "Z ${cfg.videoDir} - vdr vdr -"
+      "d ${cfg.videoDir} 0755 ${cfg.user} ${cfg.group} -"
+      "Z ${cfg.videoDir} - ${cfg.user} ${cfg.group} -"
     ];
 
     systemd.services.vdr = {
       description = "VDR";
       wantedBy = [ "multi-user.target" ];
+      wants = optional cfg.enableLirc "lircd.service";
+      after = [ "network.target" ]
+        ++ optional cfg.enableLirc "lircd.service";
       serviceConfig = {
-        ExecStart = ''
-          ${cfg.package}/bin/vdr \
-            --video="${cfg.videoDir}" \
-            --config="${libDir}" \
-            ${escapeShellArgs cfg.extraArguments}
-        '';
-        User = "vdr";
+        ExecStart =
+          let
+            args = [
+              "--video=${cfg.videoDir}"
+            ]
+            ++ optional cfg.enableLirc "--lirc=${config.passthru.lirc.socket}"
+            ++ cfg.extraArguments;
+          in
+          "${cfg.package}/bin/vdr ${lib.escapeShellArgs args}";
+        User = cfg.user;
+        Group = cfg.group;
         CacheDirectory = "vdr";
         StateDirectory = "vdr";
+        RuntimeDirectory = "vdr";
         Restart = "on-failure";
       };
     };
 
-    users.users.vdr = {
-      group = "vdr";
-      home = libDir;
-      isSystemUser = true;
+    environment.systemPackages = [ cfg.package ];
+
+    users.users = mkIf (cfg.user == "vdr") {
+      vdr = {
+        inherit (cfg) group;
+        home = "/run/vdr";
+        isSystemUser = true;
+        extraGroups = [
+          "video"
+          "audio"
+        ]
+        ++ optional cfg.enableLirc "lirc";
+      };
     };
 
-    users.groups.vdr = {};
-  }
+    users.groups = mkIf (cfg.group == "vdr") { vdr = { }; };
 
-  (mkIf cfg.enableLirc {
-    services.lirc.enable = true;
-    users.users.vdr.extraGroups = [ "lirc" ];
-    services.vdr.extraArguments = [
-      "--lirc=${config.passthru.lirc.socket}"
-    ];
-  })]);
+  };
 }
diff --git a/nixos/modules/services/home-automation/esphome.nix b/nixos/modules/services/home-automation/esphome.nix
index d7dbb6f0b90e3..4fc007a976838 100644
--- a/nixos/modules/services/home-automation/esphome.nix
+++ b/nixos/modules/services/home-automation/esphome.nix
@@ -26,12 +26,7 @@ in
   options.services.esphome = {
     enable = mkEnableOption (mdDoc "esphome");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.esphome;
-      defaultText = literalExpression "pkgs.esphome";
-      description = mdDoc "The package to use for the esphome command.";
-    };
+    package = lib.mkPackageOption pkgs "esphome" { };
 
     enableUnixSocket = mkOption {
       type = types.bool;
@@ -107,12 +102,12 @@ in
         ProtectClock = true;
         ProtectControlGroups = true;
         ProtectHome = true;
-        ProtectHostname = true;
-        ProtectKernelLogs = true;
+        ProtectHostname = false; # breaks bwrap
+        ProtectKernelLogs = false; # breaks bwrap
         ProtectKernelModules = true;
-        ProtectKernelTunables = true;
+        ProtectKernelTunables = false; # breaks bwrap
         ProtectProc = "invisible";
-        ProcSubset = "pid";
+        ProcSubset = "all"; # Using "pid" breaks bwrap
         ProtectSystem = "strict";
         #RemoveIPC = true; # Implied by DynamicUser
         RestrictAddressFamilies = [
diff --git a/nixos/modules/services/home-automation/evcc.nix b/nixos/modules/services/home-automation/evcc.nix
index d0ce3fb4a1ce6..f360f525b04b9 100644
--- a/nixos/modules/services/home-automation/evcc.nix
+++ b/nixos/modules/services/home-automation/evcc.nix
@@ -41,6 +41,7 @@ in
 
   config = mkIf cfg.enable {
     systemd.services.evcc = {
+      wants = [ "network-online.target" ];
       after = [
         "network-online.target"
         "mosquitto.target"
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index 0b8b1d7194187..a01628968966e 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -11,13 +11,12 @@ let
   # options shown in settings.
   # We post-process the result to add support for YAML functions, like secrets or includes, see e.g.
   # https://www.home-assistant.io/docs/configuration/secrets/
-  filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) cfg.config or {};
-  configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
+  filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [ null ])) (lib.recursiveUpdate customLovelaceModulesResources (cfg.config or {}));
+  configFile = pkgs.runCommandLocal "configuration.yaml" { } ''
     cp ${format.generate "configuration.yaml" filteredConfig} $out
     sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
   '';
-  lovelaceConfig = cfg.lovelaceConfig or {};
-  lovelaceConfigFile = format.generate "ui-lovelace.yaml" lovelaceConfig;
+  lovelaceConfigFile = format.generate "ui-lovelace.yaml" cfg.lovelaceConfig;
 
   # Components advertised by the home-assistant package
   availableComponents = cfg.package.availableComponents;
@@ -62,8 +61,24 @@ let
     # Respect overrides that already exist in the passed package and
     # concat it with values passed via the module.
     extraComponents = oldArgs.extraComponents or [] ++ extraComponents;
-    extraPackages = ps: (oldArgs.extraPackages or (_: []) ps) ++ (cfg.extraPackages ps);
+    extraPackages = ps: (oldArgs.extraPackages or (_: []) ps)
+      ++ (cfg.extraPackages ps)
+      ++ (lib.concatMap (component: component.propagatedBuildInputs or []) cfg.customComponents);
   }));
+
+  # Create a directory that holds all lovelace modules
+  customLovelaceModulesDir = pkgs.buildEnv {
+    name = "home-assistant-custom-lovelace-modules";
+    paths = cfg.customLovelaceModules;
+  };
+
+  # Create parts of the lovelace config that reference lovelave modules as resources
+  customLovelaceModulesResources = {
+    lovelace.resources = map (card: {
+      url = "/local/nixos-lovelace-modules/${card.entrypoint or (card.pname + ".js")}?${card.version}";
+      type = "module";
+    }) cfg.customLovelaceModules;
+  };
 in {
   imports = [
     # Migrations in NixOS 22.05
@@ -137,6 +152,41 @@ in {
       '';
     };
 
+    customComponents = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression ''
+        with pkgs.home-assistant-custom-components; [
+          prometheus_sensor
+        ];
+      '';
+      description = lib.mdDoc ''
+        List of custom component packages to install.
+
+        Available components can be found below `pkgs.home-assistant-custom-components`.
+      '';
+    };
+
+    customLovelaceModules = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExpression ''
+        with pkgs.home-assistant-custom-lovelace-modules; [
+          mini-graph-card
+          mini-media-player
+        ];
+      '';
+      description = lib.mdDoc ''
+        List of custom lovelace card packages to load as lovelace resources.
+
+        Available cards can be found below `pkgs.home-assistant-custom-lovelace-modules`.
+
+        ::: {.note}
+        Automatic loading only works with lovelace in `yaml` mode.
+        :::
+      '';
+    };
+
     config = mkOption {
       type = types.nullOr (types.submodule {
         freeformType = format.type;
@@ -385,6 +435,7 @@ in {
 
     systemd.services.home-assistant = {
       description = "Home Assistant";
+      wants = [ "network-online.target" ];
       after = [
         "network-online.target"
 
@@ -403,14 +454,40 @@ in {
           ln -s /etc/home-assistant/configuration.yaml "${cfg.configDir}/configuration.yaml"
         '';
         copyLovelaceConfig = if cfg.lovelaceConfigWritable then ''
+          rm -f "${cfg.configDir}/ui-lovelace.yaml"
           cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
         '' else ''
-          rm -f "${cfg.configDir}/ui-lovelace.yaml"
-          ln -s /etc/home-assistant/ui-lovelace.yaml "${cfg.configDir}/ui-lovelace.yaml"
+          ln -fs /etc/home-assistant/ui-lovelace.yaml "${cfg.configDir}/ui-lovelace.yaml"
+        '';
+        copyCustomLovelaceModules = if cfg.customLovelaceModules != [] then ''
+          mkdir -p "${cfg.configDir}/www"
+          ln -fns ${customLovelaceModulesDir} "${cfg.configDir}/www/nixos-lovelace-modules"
+        '' else ''
+          rm -f "${cfg.configDir}/www/nixos-lovelace-modules"
+        '';
+        copyCustomComponents = ''
+          mkdir -p "${cfg.configDir}/custom_components"
+
+          # remove components symlinked in from below the /nix/store
+          readarray -d "" components < <(find "${cfg.configDir}/custom_components" -maxdepth 1 -type l -print0)
+          for component in "''${components[@]}"; do
+            if [[ "$(readlink "$component")" =~ ^${escapeShellArg builtins.storeDir} ]]; then
+              rm "$component"
+            fi
+          done
+
+          # recreate symlinks for desired components
+          declare -a components=(${escapeShellArgs cfg.customComponents})
+          for component in "''${components[@]}"; do
+            path="$(dirname $(find "$component" -name "manifest.json"))"
+            ln -fns "$path" "${cfg.configDir}/custom_components/"
+          done
         '';
       in
         (optionalString (cfg.config != null) copyConfig) +
-        (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig)
+        (optionalString (cfg.lovelaceConfig != null) copyLovelaceConfig) +
+        copyCustomLovelaceModules +
+        copyCustomComponents
       ;
       environment.PYTHONPATH = package.pythonPath;
       serviceConfig = let
@@ -447,7 +524,6 @@ in {
           "bluetooth_tracker"
           "bthome"
           "default_config"
-          "eq3btsmart"
           "eufylife_ble"
           "esphome"
           "fjaraskupan"
@@ -455,12 +531,15 @@ in {
           "govee_ble"
           "homekit_controller"
           "inkbird"
+          "improv_ble"
           "keymitt_ble"
           "led_ble"
+          "medcom_ble"
           "melnor"
           "moat"
           "mopeka"
           "oralb"
+          "private_ble_device"
           "qingping"
           "rapt_ble"
           "ruuvi_gateway"
@@ -586,11 +665,12 @@ in {
           "~@privileged"
         ] ++ optionals (any useComponent componentsUsingPing) [
           "capset"
+          "setuid"
         ];
         UMask = "0077";
       };
       path = [
-        "/run/wrappers" # needed for ping
+        pkgs.unixtools.ping # needed for ping
       ];
     };
 
diff --git a/nixos/modules/services/home-automation/homeassistant-satellite.nix b/nixos/modules/services/home-automation/homeassistant-satellite.nix
new file mode 100644
index 0000000000000..6ca428f2af818
--- /dev/null
+++ b/nixos/modules/services/home-automation/homeassistant-satellite.nix
@@ -0,0 +1,225 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.homeassistant-satellite;
+
+  inherit (lib)
+    escapeShellArg
+    escapeShellArgs
+    mkOption
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkPackageOption
+    types
+    ;
+
+  inherit (builtins)
+    toString
+    ;
+
+  # override the package with the relevant vad dependencies
+  package = cfg.package.overridePythonAttrs (oldAttrs: {
+    propagatedBuildInputs = oldAttrs.propagatedBuildInputs
+      ++ lib.optional (cfg.vad == "webrtcvad") cfg.package.optional-dependencies.webrtc
+      ++ lib.optional (cfg.vad == "silero") cfg.package.optional-dependencies.silerovad
+      ++ lib.optional (cfg.pulseaudio.enable) cfg.package.optional-dependencies.pulseaudio;
+  });
+
+in
+
+{
+  meta.buildDocsInSandbox = false;
+
+  options.services.homeassistant-satellite = with types; {
+    enable = mkEnableOption (mdDoc "Home Assistant Satellite");
+
+    package = mkPackageOption pkgs "homeassistant-satellite" { };
+
+    user = mkOption {
+      type = str;
+      example = "alice";
+      description = mdDoc ''
+        User to run homeassistant-satellite under.
+      '';
+    };
+
+    group = mkOption {
+      type = str;
+      default = "users";
+      description = mdDoc ''
+        Group to run homeassistant-satellite under.
+      '';
+    };
+
+    host = mkOption {
+      type = str;
+      example = "home-assistant.local";
+      description = mdDoc ''
+        Hostname on which your Home Assistant instance can be reached.
+      '';
+    };
+
+    port = mkOption {
+      type = port;
+      example = 8123;
+      description = mdDoc ''
+        Port on which your Home Assistance can be reached.
+      '';
+      apply = toString;
+    };
+
+    protocol = mkOption {
+      type = enum [ "http" "https" ];
+      default = "http";
+      example = "https";
+      description = mdDoc ''
+        The transport protocol used to connect to Home Assistant.
+      '';
+    };
+
+    tokenFile = mkOption {
+      type = path;
+      example = "/run/keys/hass-token";
+      description = mdDoc ''
+        Path to a file containing a long-lived access token for your Home Assistant instance.
+      '';
+      apply = escapeShellArg;
+    };
+
+    sounds = {
+      awake = mkOption {
+        type = nullOr str;
+        default = null;
+        description = mdDoc ''
+          Audio file to play when the wake word is detected.
+        '';
+      };
+
+      done = mkOption {
+        type = nullOr str;
+        default = null;
+        description = mdDoc ''
+          Audio file to play when the voice command is done.
+        '';
+      };
+    };
+
+    vad = mkOption {
+      type = enum [ "disabled" "webrtcvad" "silero" ];
+      default = "disabled";
+      example = "silero";
+      description = mdDoc ''
+        Voice activity detection model. With `disabled` sound will be transmitted continously.
+      '';
+    };
+
+    pulseaudio = {
+      enable = mkEnableOption "recording/playback via PulseAudio or PipeWire";
+
+      socket = mkOption {
+        type = nullOr str;
+        default = null;
+        example = "/run/user/1000/pulse/native";
+        description = mdDoc ''
+          Path or hostname to connect with the PulseAudio server.
+        '';
+      };
+
+      duckingVolume = mkOption {
+        type = nullOr float;
+        default = null;
+        example = 0.4;
+        description = mdDoc ''
+          Reduce output volume (between 0 and 1) to this percentage value while recording.
+        '';
+      };
+
+      echoCancellation = mkEnableOption "acoustic echo cancellation";
+    };
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [ ];
+      description = mdDoc ''
+        Extra arguments to pass to the commandline.
+      '';
+      apply = escapeShellArgs;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services."homeassistant-satellite" = {
+      description = "Home Assistant Satellite";
+      after = [
+        "network-online.target"
+      ];
+      wants = [
+        "network-online.target"
+      ];
+      wantedBy = [
+        "multi-user.target"
+      ];
+      path = with pkgs; [
+        ffmpeg-headless
+      ] ++ lib.optionals (!cfg.pulseaudio.enable) [
+        alsa-utils
+      ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        # https://github.com/rhasspy/hassio-addons/blob/master/assist_microphone/rootfs/etc/s6-overlay/s6-rc.d/assist_microphone/run
+        ExecStart = ''
+          ${package}/bin/homeassistant-satellite \
+            --host ${cfg.host} \
+            --port ${cfg.port} \
+            --protocol ${cfg.protocol} \
+            --token-file ${cfg.tokenFile} \
+            --vad ${cfg.vad} \
+            ${lib.optionalString cfg.pulseaudio.enable "--pulseaudio"}${lib.optionalString (cfg.pulseaudio.socket != null) "=${cfg.pulseaudio.socket}"} \
+            ${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.duckingVolume != null) "--ducking-volume=${toString cfg.pulseaudio.duckingVolume}"} \
+            ${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.echoCancellation) "--echo-cancel"} \
+            ${lib.optionalString (cfg.sounds.awake != null) "--awake-sound=${toString cfg.sounds.awake}"} \
+            ${lib.optionalString (cfg.sounds.done != null) "--done-sound=${toString cfg.sounds.done}"} \
+            ${cfg.extraArgs}
+        '';
+        CapabilityBoundingSet = "";
+        DeviceAllow = "";
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Operation not permitted
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHome = false; # Would deny access to local pulse/pipewire server
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectProc = "invisible";
+        ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo
+        Restart = "always";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SupplementaryGroups = [
+          "audio"
+        ];
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/home-automation/zigbee2mqtt.nix b/nixos/modules/services/home-automation/zigbee2mqtt.nix
index 796de3a491e49..a653e49a09f62 100644
--- a/nixos/modules/services/home-automation/zigbee2mqtt.nix
+++ b/nixos/modules/services/home-automation/zigbee2mqtt.nix
@@ -20,14 +20,7 @@ in
   options.services.zigbee2mqtt = {
     enable = mkEnableOption (lib.mdDoc "zigbee2mqtt service");
 
-    package = mkOption {
-      description = lib.mdDoc "Zigbee2mqtt package to use";
-      default = pkgs.zigbee2mqtt;
-      defaultText = literalExpression ''
-        pkgs.zigbee2mqtt
-      '';
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "zigbee2mqtt" { };
 
     dataDir = mkOption {
       description = lib.mdDoc "Zigbee2mqtt data directory";
@@ -66,9 +59,10 @@ in
         server = mkDefault "mqtt://localhost:1883";
       };
       serial.port = mkDefault "/dev/ttyACM0";
-      # reference device configuration, that is kept in a separate file
+      # reference device/group configuration, that is kept in a separate file
       # to prevent it being overwritten in the units ExecStartPre script
       devices = mkDefault "devices.yaml";
+      groups = mkDefault "groups.yaml";
     };
 
     systemd.services.zigbee2mqtt = {
diff --git a/nixos/modules/services/home-automation/zwave-js.nix b/nixos/modules/services/home-automation/zwave-js.nix
new file mode 100644
index 0000000000000..9821da7ef6edd
--- /dev/null
+++ b/nixos/modules/services/home-automation/zwave-js.nix
@@ -0,0 +1,152 @@
+{config, pkgs, lib, ...}:
+
+with lib;
+
+let
+  cfg = config.services.zwave-js;
+  mergedConfigFile = "/run/zwave-js/config.json";
+  settingsFormat = pkgs.formats.json {};
+in {
+  options.services.zwave-js = {
+    enable = mkEnableOption (mdDoc "the zwave-js server on boot");
+
+    package = mkPackageOption pkgs "zwave-js-server" { };
+
+    port = mkOption {
+      type = types.port;
+      default = 3000;
+      description = mdDoc ''
+        Port for the server to listen on.
+      '';
+    };
+
+    serialPort = mkOption {
+      type = types.path;
+      description = mdDoc ''
+        Serial port device path for Z-Wave controller.
+      '';
+      example = "/dev/ttyUSB0";
+    };
+
+    secretsConfigFile = mkOption {
+      type = types.path;
+      description = mdDoc ''
+        JSON file containing secret keys. A dummy example:
+
+        ```
+        {
+          "securityKeys": {
+            "S0_Legacy": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
+            "S2_Unauthenticated": "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB",
+            "S2_Authenticated": "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC",
+            "S2_AccessControl": "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
+          }
+        }
+        ```
+
+        See
+        <https://zwave-js.github.io/node-zwave-js/#/getting-started/security-s2>
+        for details. This file will be merged with the module-generated config
+        file (taking precedence).
+
+        Z-Wave keys can be generated with:
+
+          {command}`< /dev/urandom tr -dc A-F0-9 | head -c32 ;echo`
+
+
+        ::: {.warning}
+        A file in the nix store should not be used since it will be readable to
+        all users.
+        :::
+      '';
+      example = "/secrets/zwave-js-keys.json";
+    };
+
+    settings = mkOption {
+      type = lib.types.submodule {
+        freeformType = settingsFormat.type;
+
+        options = {
+          storage = {
+            cacheDir = mkOption {
+              type = types.path;
+              default = "/var/cache/zwave-js";
+              readOnly = true;
+              description = lib.mdDoc "Cache directory";
+            };
+          };
+        };
+      };
+      default = {};
+      description = mdDoc ''
+        Configuration settings for the generated config
+        file.
+      '';
+    };
+
+    extraFlags = lib.mkOption {
+      type = with lib.types; listOf str;
+      default = [ ];
+      example = [ "--mock-driver" ];
+      description = lib.mdDoc ''
+        Extra flags to pass to command
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.zwave-js = let
+      configFile = settingsFormat.generate "zwave-js-config.json" cfg.settings;
+    in {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = "Z-Wave JS Server";
+      serviceConfig = {
+        ExecStartPre = ''
+          /bin/sh -c "${pkgs.jq}/bin/jq -s '.[0] * .[1]' ${configFile} ${cfg.secretsConfigFile} > ${mergedConfigFile}"
+        '';
+        ExecStart = lib.concatStringsSep " " [
+          "${cfg.package}/bin/zwave-server"
+          "--config ${mergedConfigFile}"
+          "--port ${toString cfg.port}"
+          cfg.serialPort
+          (escapeShellArgs cfg.extraFlags)
+        ];
+        Restart = "on-failure";
+        User = "zwave-js";
+        SupplementaryGroups = [ "dialout" ];
+        CacheDirectory = "zwave-js";
+        RuntimeDirectory = "zwave-js";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        DeviceAllow = [cfg.serialPort];
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = false;
+        NoNewPrivileges = true;
+        PrivateUsers = true;
+        PrivateTmp = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        RemoveIPC = true;
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service @pkey"
+          "~@privileged @resources"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ graham33 ];
+}
diff --git a/nixos/modules/services/logging/SystemdJournal2Gelf.nix b/nixos/modules/services/logging/SystemdJournal2Gelf.nix
index 3d85c2b62c634..429dde33b521e 100644
--- a/nixos/modules/services/logging/SystemdJournal2Gelf.nix
+++ b/nixos/modules/services/logging/SystemdJournal2Gelf.nix
@@ -33,14 +33,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.systemd-journal2gelf;
-        defaultText = literalExpression "pkgs.systemd-journal2gelf";
-        description = lib.mdDoc ''
-          SystemdJournal2Gelf package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "systemd-journal2gelf" { };
 
     };
   };
diff --git a/nixos/modules/services/logging/filebeat.nix b/nixos/modules/services/logging/filebeat.nix
index 5b5e7fd5ae894..071e001eb3c56 100644
--- a/nixos/modules/services/logging/filebeat.nix
+++ b/nixos/modules/services/logging/filebeat.nix
@@ -5,6 +5,7 @@ let
     attrValues
     literalExpression
     mkEnableOption
+    mkPackageOption
     mkIf
     mkOption
     types;
@@ -20,14 +21,8 @@ in
 
       enable = mkEnableOption (lib.mdDoc "filebeat");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.filebeat;
-        defaultText = literalExpression "pkgs.filebeat";
-        example = literalExpression "pkgs.filebeat7";
-        description = lib.mdDoc ''
-          The filebeat package to use.
-        '';
+      package = mkPackageOption pkgs "filebeat" {
+        example = "filebeat7";
       };
 
       inputs = mkOption {
diff --git a/nixos/modules/services/logging/fluentd.nix b/nixos/modules/services/logging/fluentd.nix
index 7764aafb2d1af..c8718f26db383 100644
--- a/nixos/modules/services/logging/fluentd.nix
+++ b/nixos/modules/services/logging/fluentd.nix
@@ -20,12 +20,7 @@ in {
         description = lib.mdDoc "Fluentd config.";
       };
 
-      package = mkOption {
-        type = types.path;
-        default = pkgs.fluentd;
-        defaultText = literalExpression "pkgs.fluentd";
-        description = lib.mdDoc "The fluentd package to use.";
-      };
+      package = mkPackageOption pkgs "fluentd" { };
 
       plugins = mkOption {
         type = types.listOf types.path;
diff --git a/nixos/modules/services/logging/heartbeat.nix b/nixos/modules/services/logging/heartbeat.nix
index a9ae11ec66e60..768ffe5315fe0 100644
--- a/nixos/modules/services/logging/heartbeat.nix
+++ b/nixos/modules/services/logging/heartbeat.nix
@@ -20,14 +20,8 @@ in
 
       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.
-        '';
+      package = mkPackageOption pkgs "heartbeat" {
+        example = "heartbeat7";
       };
 
       name = mkOption {
diff --git a/nixos/modules/services/logging/journalbeat.nix b/nixos/modules/services/logging/journalbeat.nix
index e761380552dea..80933d6a0f96c 100644
--- a/nixos/modules/services/logging/journalbeat.nix
+++ b/nixos/modules/services/logging/journalbeat.nix
@@ -20,14 +20,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "journalbeat");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.journalbeat;
-        defaultText = literalExpression "pkgs.journalbeat";
-        description = lib.mdDoc ''
-          The journalbeat package to use
-        '';
-      };
+      package = mkPackageOption pkgs "journalbeat" { };
 
       name = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/logging/journaldriver.nix b/nixos/modules/services/logging/journaldriver.nix
index 59eedff90d60e..4d21464018aac 100644
--- a/nixos/modules/services/logging/journaldriver.nix
+++ b/nixos/modules/services/logging/journaldriver.nix
@@ -84,6 +84,7 @@ in {
     systemd.services.journaldriver = {
       description = "Stackdriver Logging journal forwarder";
       script      = "${pkgs.journaldriver}/bin/journaldriver";
+      wants       = [ "network-online.target" ];
       after       = [ "network-online.target" ];
       wantedBy    = [ "multi-user.target" ];
 
diff --git a/nixos/modules/services/logging/logcheck.nix b/nixos/modules/services/logging/logcheck.nix
index 8a277cea6e461..5d87fc87d4161 100644
--- a/nixos/modules/services/logging/logcheck.nix
+++ b/nixos/modules/services/logging/logcheck.nix
@@ -220,10 +220,16 @@ in
       logcheck = {};
     };
 
-    system.activationScripts.logcheck = ''
-      mkdir -m 700 -p /var/{lib,lock}/logcheck
-      chown ${cfg.user} /var/{lib,lock}/logcheck
-    '';
+    systemd.tmpfiles.settings.logcheck = {
+      "/var/lib/logcheck".d = {
+        mode = "700";
+        inherit (cfg) user;
+      };
+      "/var/lock/logcheck".d = {
+        mode = "700";
+        inherit (cfg) user;
+      };
+    };
 
     services.cron.systemCronJobs =
         let withTime = name: {timeArgs, ...}: timeArgs != null;
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index 342ac5ec6e049..ba1445f083975 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
@@ -220,6 +220,12 @@ in
           in this case you can disable the failing check with this option.
         '';
       };
+
+      extraArgs = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [];
+        description = "Additional command line arguments to pass on logrotate invocation";
+      };
     };
   };
 
@@ -231,7 +237,7 @@ in
       serviceConfig = {
         Restart = "no";
         User = "root";
-        ExecStart = "${pkgs.logrotate}/sbin/logrotate ${mailOption} ${cfg.configFile}";
+        ExecStart = "${pkgs.logrotate}/sbin/logrotate ${utils.escapeSystemdExecArgs cfg.extraArgs} ${mailOption} ${cfg.configFile}";
       };
     };
     systemd.services.logrotate-checkconf = {
@@ -240,7 +246,7 @@ in
       serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = true;
-        ExecStart = "${pkgs.logrotate}/sbin/logrotate --debug ${cfg.configFile}";
+        ExecStart = "${pkgs.logrotate}/sbin/logrotate ${utils.escapeSystemdExecArgs cfg.extraArgs} --debug ${cfg.configFile}";
       };
     };
   };
diff --git a/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix
index e9e3ae1f14ce7..22292dbd931b0 100644
--- a/nixos/modules/services/logging/logstash.nix
+++ b/nixos/modules/services/logging/logstash.nix
@@ -54,12 +54,7 @@ in
         description = lib.mdDoc "Enable logstash.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.logstash;
-        defaultText = literalExpression "pkgs.logstash";
-        description = lib.mdDoc "Logstash package to use.";
-      };
+      package = mkPackageOption pkgs "logstash" { };
 
       plugins = mkOption {
         type = types.listOf types.path;
@@ -123,7 +118,7 @@ in
         example = ''
           if [type] == "syslog" {
             # Keep only relevant systemd fields
-            # http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
+            # https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
             prune {
               whitelist_names => [
                 "type", "@timestamp", "@version",
diff --git a/nixos/modules/services/logging/syslog-ng.nix b/nixos/modules/services/logging/syslog-ng.nix
index d22acbeaa70c5..eea236263f7ea 100644
--- a/nixos/modules/services/logging/syslog-ng.nix
+++ b/nixos/modules/services/logging/syslog-ng.nix
@@ -40,14 +40,7 @@ in {
           Whether to enable the syslog-ng daemon.
         '';
       };
-      package = mkOption {
-        type = types.package;
-        default = pkgs.syslogng;
-        defaultText = literalExpression "pkgs.syslogng";
-        description = lib.mdDoc ''
-          The package providing syslog-ng binaries.
-        '';
-      };
+      package = mkPackageOption pkgs "syslogng" { };
       extraModulePaths = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -67,7 +60,7 @@ in {
       configHeader = mkOption {
         type = types.lines;
         default = ''
-          @version: 3.6
+          @version: 4.4
           @include "scl.conf"
         '';
         description = lib.mdDoc ''
diff --git a/nixos/modules/services/logging/ulogd.nix b/nixos/modules/services/logging/ulogd.nix
index 065032b531c6d..05c9797bb28bc 100644
--- a/nixos/modules/services/logging/ulogd.nix
+++ b/nixos/modules/services/logging/ulogd.nix
@@ -3,7 +3,7 @@
 with lib;
 let
   cfg = config.services.ulogd;
-  settingsFormat = pkgs.formats.ini { };
+  settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; };
   settingsFile = settingsFormat.generate "ulogd.conf" cfg.settings;
 in {
   options = {
@@ -12,22 +12,34 @@ in {
 
       settings = mkOption {
         example = {
-          global.stack = "stack=log1:NFLOG,base1:BASE,pcap1:PCAP";
+          global.stack = [
+            "log1:NFLOG,base1:BASE,ifi1:IFINDEX,ip2str1:IP2STR,print1:PRINTPKT,emu1:LOGEMU"
+            "log1:NFLOG,base1:BASE,pcap1:PCAP"
+          ];
+
           log1.group = 2;
+
           pcap1 = {
+            sync = 1;
             file = "/var/log/ulogd.pcap";
+          };
+
+          emu1 = {
             sync = 1;
+            file = "/var/log/ulogd_pkts.log";
           };
         };
         type = settingsFormat.type;
         default = { };
-        description = lib.mdDoc "Configuration for ulogd. See {file}`/share/doc/ulogd/` in `pkgs.ulogd.doc`.";
+        description = lib.mdDoc
+          "Configuration for ulogd. See {file}`/share/doc/ulogd/` in `pkgs.ulogd.doc`.";
       };
 
       logLevel = mkOption {
         type = types.enum [ 1 3 5 7 8 ];
         default = 5;
-        description = lib.mdDoc "Log level (1 = debug, 3 = info, 5 = notice, 7 = error, 8 = fatal)";
+        description = lib.mdDoc
+          "Log level (1 = debug, 3 = info, 5 = notice, 7 = error, 8 = fatal)";
       };
     };
   };
@@ -40,7 +52,10 @@ in {
       before = [ "network-pre.target" ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.ulogd}/bin/ulogd -c ${settingsFile} --verbose --loglevel ${toString cfg.logLevel}";
+        ExecStart =
+          "${pkgs.ulogd}/bin/ulogd -c ${settingsFile} --verbose --loglevel ${
+            toString cfg.logLevel
+          }";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
       };
     };
diff --git a/nixos/modules/services/logging/vector.nix b/nixos/modules/services/logging/vector.nix
index f2edeabfc06f0..9ccf8a4fa0610 100644
--- a/nixos/modules/services/logging/vector.nix
+++ b/nixos/modules/services/logging/vector.nix
@@ -8,7 +8,7 @@ in
   options.services.vector = {
     enable = mkEnableOption (lib.mdDoc "Vector");
 
-    package = mkPackageOptionMD pkgs "vector" { };
+    package = mkPackageOption pkgs "vector" { };
 
     journaldAccess = mkOption {
       type = types.bool;
@@ -51,13 +51,17 @@ in
         {
           ExecStart = "${getExe cfg.package} --config ${validateConfig conf}";
           DynamicUser = true;
-          Restart = "no";
+          Restart = "always";
           StateDirectory = "vector";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           AmbientCapabilities = "CAP_NET_BIND_SERVICE";
           # This group is required for accessing journald.
           SupplementaryGroups = mkIf cfg.journaldAccess "systemd-journal";
         };
+      unitConfig = {
+        StartLimitIntervalSec = 10;
+        StartLimitBurst = 5;
+      };
     };
   };
 }
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index 21bafd859c3c2..25c7017a1d258 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -1,8 +1,11 @@
 { options, config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib) any attrValues concatMapStringsSep concatStrings
+    concatStringsSep flatten imap1 isList literalExpression mapAttrsToList
+    mkEnableOption mkIf mkOption mkRemovedOptionModule optional optionalAttrs
+    optionalString singleton types;
+
   cfg = config.services.dovecot2;
   dovecotPkg = pkgs.dovecot;
 
@@ -113,6 +116,36 @@ let
       ''
     )
 
+    ''
+      plugin {
+        sieve_plugins = ${concatStringsSep " " cfg.sieve.plugins}
+        sieve_extensions = ${concatStringsSep " " (map (el: "+${el}") cfg.sieve.extensions)}
+        sieve_global_extensions = ${concatStringsSep " " (map (el: "+${el}") cfg.sieve.globalExtensions)}
+    ''
+    (optionalString (cfg.imapsieve.mailbox != []) ''
+      ${
+        concatStringsSep "\n" (flatten (imap1 (
+            idx: el:
+              singleton "imapsieve_mailbox${toString idx}_name = ${el.name}"
+              ++ optional (el.from != null) "imapsieve_mailbox${toString idx}_from = ${el.from}"
+              ++ optional (el.causes != null) "imapsieve_mailbox${toString idx}_causes = ${el.causes}"
+              ++ optional (el.before != null) "imapsieve_mailbox${toString idx}_before = file:${stateDir}/imapsieve/before/${baseNameOf el.before}"
+              ++ optional (el.after != null) "imapsieve_mailbox${toString idx}_after = file:${stateDir}/imapsieve/after/${baseNameOf el.after}"
+          )
+          cfg.imapsieve.mailbox))
+      }
+    '')
+    (optionalString (cfg.sieve.pipeBins != []) ''
+        sieve_pipe_bin_dir = ${pkgs.linkFarm "sieve-pipe-bins" (map (el: {
+          name = builtins.unsafeDiscardStringContext (baseNameOf el);
+          path = el;
+        })
+        cfg.sieve.pipeBins)}
+    '')
+    ''
+      }
+    ''
+
     cfg.extraConfig
   ];
 
@@ -302,7 +335,7 @@ in
 
     enablePAM = mkEnableOption (lib.mdDoc "creating a own Dovecot PAM service and configure PAM user logins") // { default = true; };
 
-    enableDHE = mkEnableOption (lib.mdDoc "enable ssl_dh and generation of primes for the key exchange") // { default = true; };
+    enableDHE = mkEnableOption (lib.mdDoc "ssl_dh and generation of primes for the key exchange") // { default = true; };
 
     sieveScripts = mkOption {
       type = types.attrsOf types.path;
@@ -343,6 +376,104 @@ in
       description = lib.mdDoc "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
     };
 
+    imapsieve.mailbox = mkOption {
+      default = [];
+      description = "Configure Sieve filtering rules on IMAP actions";
+      type = types.listOf (types.submodule ({ config, ... }: {
+        options = {
+          name = mkOption {
+            description = ''
+              This setting configures the name of a mailbox for which administrator scripts are configured.
+
+              The settings defined hereafter with matching sequence numbers apply to the mailbox named by this setting.
+
+              This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes.
+            '';
+            example = "Junk";
+            type = types.str;
+          };
+
+          from = mkOption {
+            default = null;
+            description = ''
+              Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot2.imapsieve.mailbox.<name>.name when the message originates from the indicated mailbox.
+
+              This setting supports wildcards with a syntax compatible with the IMAP LIST command, meaning that this setting can apply to multiple or even all ("*") mailboxes.
+            '';
+            example = "*";
+            type = types.nullOr types.str;
+          };
+
+          causes = mkOption {
+            default = null;
+            description = ''
+              Only execute the administrator Sieve scripts for the mailbox configured with services.dovecot2.imapsieve.mailbox.<name>.name when one of the listed IMAPSIEVE causes apply.
+
+              This has no effect on the user script, which is always executed no matter the cause.
+            '';
+            example = "COPY";
+            type = types.nullOr (types.enum [ "APPEND" "COPY" "FLAG" ]);
+          };
+
+          before = mkOption {
+            default = null;
+            description = ''
+              When an IMAP event of interest occurs, this sieve script is executed before any user script respectively.
+
+              This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_before: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed.
+            '';
+            example = literalExpression "./report-spam.sieve";
+            type = types.nullOr types.path;
+          };
+
+          after = mkOption {
+            default = null;
+            description = ''
+              When an IMAP event of interest occurs, this sieve script is executed after any user script respectively.
+
+              This setting each specify the location of a single sieve script. The semantics of this setting is similar to sieve_after: the specified scripts form a sequence together with the user script in which the next script is only executed when an (implicit) keep action is executed.
+            '';
+            example = literalExpression "./report-spam.sieve";
+            type = types.nullOr types.path;
+          };
+        };
+      }));
+    };
+
+    sieve = {
+      plugins = mkOption {
+        default = [];
+        example = [ "sieve_extprograms" ];
+        description = "Sieve plugins to load";
+        type = types.listOf types.str;
+      };
+
+      extensions = mkOption {
+        default = [];
+        description = "Sieve extensions for use in user scripts";
+        example = [ "notify" "imapflags" "vnd.dovecot.filter" ];
+        type = types.listOf types.str;
+      };
+
+      globalExtensions = mkOption {
+        default = [];
+        example = [ "vnd.dovecot.environment" ];
+        description = "Sieve extensions for use in global scripts";
+        type = types.listOf types.str;
+      };
+
+      pipeBins = mkOption {
+        default = [];
+        example = literalExpression ''
+          map lib.getExe [
+            (pkgs.writeShellScriptBin "learn-ham.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_ham")
+            (pkgs.writeShellScriptBin "learn-spam.sh" "exec ''${pkgs.rspamd}/bin/rspamc learn_spam")
+          ]
+        '';
+        description = "Programs available for use by the vnd.dovecot.pipe extension";
+        type = types.listOf types.path;
+      };
+    };
   };
 
 
@@ -353,14 +484,23 @@ in
       enable = true;
       params.dovecot2 = {};
     };
-    services.dovecot2.protocols =
-      optional cfg.enableImap "imap"
-      ++ optional cfg.enablePop3 "pop3"
-      ++ optional cfg.enableLmtp "lmtp";
-
-    services.dovecot2.mailPlugins = mkIf cfg.enableQuota {
-      globally.enable = [ "quota" ];
-      perProtocol.imap.enable = [ "imap_quota" ];
+
+    services.dovecot2 = {
+      protocols =
+        optional cfg.enableImap "imap"
+        ++ optional cfg.enablePop3 "pop3"
+        ++ optional cfg.enableLmtp "lmtp";
+
+      mailPlugins = mkIf cfg.enableQuota {
+        globally.enable = [ "quota" ];
+        perProtocol.imap.enable = [ "imap_quota" ];
+      };
+
+      sieve.plugins =
+        optional (cfg.imapsieve.mailbox != []) "sieve_imapsieve"
+        ++ optional (cfg.sieve.pipeBins != []) "sieve_extprograms";
+
+      sieve.globalExtensions = optional (cfg.sieve.pipeBins != []) "vnd.dovecot.pipe";
     };
 
     users.users = {
@@ -415,7 +555,7 @@ in
       # (should be 0) so that the compiled sieve script is newer than
       # the source file and Dovecot won't try to compile it.
       preStart = ''
-        rm -rf ${stateDir}/sieve
+        rm -rf ${stateDir}/sieve ${stateDir}/imapsieve
       '' + optionalString (cfg.sieveScripts != {}) ''
         mkdir -p ${stateDir}/sieve
         ${concatStringsSep "\n" (
@@ -432,6 +572,29 @@ in
         ) cfg.sieveScripts
       )}
         chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve'
+      ''
+      + optionalString (cfg.imapsieve.mailbox != []) ''
+        mkdir -p ${stateDir}/imapsieve/{before,after}
+
+        ${
+          concatMapStringsSep "\n"
+            (el:
+              optionalString (el.before != null) ''
+                cp -p ${el.before} ${stateDir}/imapsieve/before/${baseNameOf el.before}
+                ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/before/${baseNameOf el.before}'
+              ''
+              + optionalString (el.after != null) ''
+                cp -p ${el.after} ${stateDir}/imapsieve/after/${baseNameOf el.after}
+                ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/imapsieve/after/${baseNameOf el.after}'
+              ''
+            )
+            cfg.imapsieve.mailbox
+        }
+
+        ${
+          optionalString (cfg.mailUser != null && cfg.mailGroup != null)
+            "chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/imapsieve'"
+        }
       '';
     };
 
@@ -459,4 +622,5 @@ in
 
   };
 
+  meta.maintainers = [ lib.maintainers.dblsaiko ];
 }
diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix
index 1d1258913b674..63d3fa54b23d5 100644
--- a/nixos/modules/services/mail/exim.nix
+++ b/nixos/modules/services/mail/exim.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) literalExpression mkIf mkOption singleton types;
+  inherit (lib) literalExpression mkIf mkOption singleton types mkPackageOption;
   inherit (pkgs) coreutils;
   cfg = config.services.exim;
 in
@@ -57,12 +57,8 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.exim;
-        defaultText = literalExpression "pkgs.exim";
-        description = lib.mdDoc ''
-          The Exim derivation to use.
+      package = mkPackageOption pkgs "exim" {
+        extraDescription = ''
           This can be used to enable features such as LDAP or PAM support.
         '';
       };
diff --git a/nixos/modules/services/mail/listmonk.nix b/nixos/modules/services/mail/listmonk.nix
index 251362fdd89d3..945eb436c1f23 100644
--- a/nixos/modules/services/mail/listmonk.nix
+++ b/nixos/modules/services/mail/listmonk.nix
@@ -54,7 +54,7 @@ let
 
       smtp = mkOption {
         type = listOf (submodule {
-          freeformType = with types; attrsOf (oneOf [ str int bool ]);
+          freeformType = with types; attrsOf anything;
 
           options = {
             enabled = mkEnableOption (lib.mdDoc "this SMTP server for listmonk");
@@ -86,7 +86,7 @@ let
       # TODO: refine this type based on the smtp one.
       "bounce.mailboxes" = mkOption {
         type = listOf
-          (submodule { freeformType = with types; oneOf [ str int bool ]; });
+          (submodule { freeformType = with types; listOf (attrsOf anything); });
         default = [ ];
         description = lib.mdDoc "List of bounce mailboxes";
       };
@@ -128,7 +128,7 @@ in {
           '';
         };
       };
-      package = mkPackageOptionMD pkgs "listmonk" {};
+      package = mkPackageOption pkgs "listmonk" {};
       settings = mkOption {
         type = types.submodule { freeformType = tomlFormat.type; };
         description = lib.mdDoc ''
@@ -168,7 +168,7 @@ in {
 
       ensureUsers = [{
         name = "listmonk";
-        ensurePermissions = { "DATABASE listmonk" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
 
       ensureDatabases = [ "listmonk" ];
@@ -201,13 +201,12 @@ in {
         DynamicUser = true;
         NoNewPrivileges = true;
         CapabilityBoundingSet = "";
-        SystemCallArchitecture = "native";
+        SystemCallArchitectures = "native";
         SystemCallFilter = [ "@system-service" "~@privileged" ];
-        ProtectDevices = true;
+        PrivateDevices = true;
         ProtectControlGroups = true;
         ProtectKernelTunables = true;
         ProtectHome = true;
-        DeviceAllow = false;
         RestrictNamespaces = true;
         RestrictRealtime = true;
         UMask = "0027";
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index ec2a19f58bb11..76035625fbe17 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -260,7 +260,7 @@ in {
       };
 
       serve = {
-        enable = mkEnableOption (lib.mdDoc "Automatic nginx and uwsgi setup for mailman-web");
+        enable = mkEnableOption (lib.mdDoc "automatic nginx and uwsgi setup for mailman-web");
 
         virtualRoot = mkOption {
           default = "/";
@@ -314,7 +314,7 @@ in {
         queue_dir = "$var_dir/queue";
         template_dir = "$var_dir/templates";
         log_dir = "/var/log/mailman";
-        lock_dir = "$var_dir/lock";
+        lock_dir = "/run/mailman/lock";
         etc_dir = "/etc";
         pid_file = "/run/mailman/master.pid";
       };
@@ -493,6 +493,9 @@ in {
           RuntimeDirectory = "mailman";
           LogsDirectory = "mailman";
           PIDFile = "/run/mailman/master.pid";
+          Restart = "on-failure";
+          TimeoutStartSec = 180;
+          TimeoutStopSec = 180;
         };
       };
 
@@ -592,10 +595,11 @@ in {
           # Since the mailman-web settings.py obstinately creates a logs
           # dir in the cwd, change to the (writable) runtime directory before
           # starting uwsgi.
-          ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${pkgs.uwsgi.override { plugins = ["python3"]; }}/bin/uwsgi --json ${uwsgiConfigFile}";
+          ExecStart = "${pkgs.coreutils}/bin/env -C $RUNTIME_DIRECTORY ${pkgs.uwsgi.override { plugins = ["python3"]; python3 = webEnv.python; }}/bin/uwsgi --json ${uwsgiConfigFile}";
           User = cfg.webUser;
           Group = "mailman";
           RuntimeDirectory = "mailman-uwsgi";
+          Restart = "on-failure";
         };
       });
 
@@ -620,6 +624,7 @@ in {
           User = cfg.webUser;
           Group = "mailman";
           WorkingDirectory = "/var/lib/mailman-web";
+          Restart = "on-failure";
         };
       };
     } // flip lib.mapAttrs' {
@@ -644,7 +649,7 @@ in {
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ lheckemann qyliss ma27 ];
+    maintainers = with lib.maintainers; [ lheckemann qyliss ];
     doc = ./mailman.md;
   };
 
diff --git a/nixos/modules/services/mail/mlmmj.nix b/nixos/modules/services/mail/mlmmj.nix
index 642f8b20fe355..3f07fabcf1771 100644
--- a/nixos/modules/services/mail/mlmmj.nix
+++ b/nixos/modules/services/mail/mlmmj.nix
@@ -143,13 +143,11 @@ in
 
     environment.systemPackages = [ pkgs.mlmmj ];
 
-    system.activationScripts.mlmmj = ''
-          ${pkgs.coreutils}/bin/mkdir -p ${stateDir} ${spoolDir}/${cfg.listDomain}
-          ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${spoolDir}
-          ${concatMapLines (createList cfg.listDomain) cfg.mailLists}
-          ${pkgs.postfix}/bin/postmap /etc/postfix/virtual
-          ${pkgs.postfix}/bin/postmap /etc/postfix/transport
-      '';
+    systemd.tmpfiles.rules = [
+      ''d "${stateDir}" -''
+      ''d "${spoolDir}/${cfg.listDomain}" -''
+      ''Z "${spoolDir}" - "${cfg.user}" "${cfg.group}" -''
+    ];
 
     systemd.services.mlmmj-maintd = {
       description = "mlmmj maintenance daemon";
@@ -158,6 +156,11 @@ in
         Group = cfg.group;
         ExecStart = "${pkgs.mlmmj}/bin/mlmmj-maintd -F -d ${spoolDir}/${cfg.listDomain}";
       };
+      preStart = ''
+        ${concatMapLines (createList cfg.listDomain) cfg.mailLists}
+        ${pkgs.postfix}/bin/postmap /etc/postfix/virtual
+        ${pkgs.postfix}/bin/postmap /etc/postfix/transport
+      '';
     };
 
     systemd.timers.mlmmj-maintd = {
diff --git a/nixos/modules/services/mail/nullmailer.nix b/nixos/modules/services/mail/nullmailer.nix
index f6befe246b12a..4fd0026dbe4eb 100644
--- a/nixos/modules/services/mail/nullmailer.nix
+++ b/nixos/modules/services/mail/nullmailer.nix
@@ -120,7 +120,7 @@ with lib;
         };
 
         maxpause = mkOption {
-          type = types.nullOr types.str;
+          type = with types; nullOr (oneOf [ str int ]);
           default = null;
           description = lib.mdDoc ''
              The maximum time to pause between successive queue runs, in seconds.
@@ -138,7 +138,7 @@ with lib;
         };
 
         pausetime = mkOption {
-          type = types.nullOr types.str;
+          type = with types; nullOr (oneOf [ str int ]);
           default = null;
           description = lib.mdDoc ''
             The minimum time to pause between successive queue runs when there
@@ -168,7 +168,7 @@ with lib;
         };
 
         sendtimeout = mkOption {
-          type = types.nullOr types.str;
+          type = with types; nullOr (oneOf [ str int ]);
           default = null;
           description = lib.mdDoc ''
             The  time to wait for a remote module listed above to complete sending
@@ -194,7 +194,7 @@ with lib;
     environment = {
       systemPackages = [ pkgs.nullmailer ];
       etc = let
-        validAttrs = filterAttrs (name: value: value != null) cfg.config;
+        validAttrs = lib.mapAttrs (_: toString) (filterAttrs (_: value: value != null) cfg.config);
       in
         (foldl' (as: name: as // { "nullmailer/${name}".text = validAttrs.${name}; }) {} (attrNames validAttrs))
           // optionalAttrs (cfg.remotesFile != null) { "nullmailer/remotes".source = cfg.remotesFile; };
diff --git a/nixos/modules/services/mail/offlineimap.nix b/nixos/modules/services/mail/offlineimap.nix
index 64fa09e836120..0166ec4e8d4e1 100644
--- a/nixos/modules/services/mail/offlineimap.nix
+++ b/nixos/modules/services/mail/offlineimap.nix
@@ -22,12 +22,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.offlineimap;
-      defaultText = literalExpression "pkgs.offlineimap";
-      description = lib.mdDoc "Offlineimap derivation to use.";
-    };
+    package = mkPackageOption pkgs "offlineimap" { };
 
     path = mkOption {
       type = types.listOf types.path;
diff --git a/nixos/modules/services/mail/opensmtpd.nix b/nixos/modules/services/mail/opensmtpd.nix
index 6ad3386d2d4eb..a65c8e05a9ce8 100644
--- a/nixos/modules/services/mail/opensmtpd.nix
+++ b/nixos/modules/services/mail/opensmtpd.nix
@@ -31,12 +31,7 @@ in {
         description = lib.mdDoc "Whether to enable the OpenSMTPD server.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.opensmtpd;
-        defaultText = literalExpression "pkgs.opensmtpd";
-        description = lib.mdDoc "The OpenSMTPD package to use.";
-      };
+      package = mkPackageOption pkgs "opensmtpd" { };
 
       setSendmail = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index 23c47aaca7e23..209e066a19ef8 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -747,7 +747,7 @@ in
 
             ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
               ln -sf ${from} /var/lib/postfix/conf/${to}
-              ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to}
+              ${pkgs.postfix}/bin/postalias -o -p /var/lib/postfix/conf/${to}
             '') cfg.aliasFiles)}
             ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
               ln -sf ${from} /var/lib/postfix/conf/${to}
@@ -779,6 +779,19 @@ in
             ExecStart = "${pkgs.postfix}/bin/postfix start";
             ExecStop = "${pkgs.postfix}/bin/postfix stop";
             ExecReload = "${pkgs.postfix}/bin/postfix reload";
+
+            # Hardening
+            PrivateTmp = true;
+            PrivateDevices = true;
+            ProtectSystem = "full";
+            CapabilityBoundingSet = [ "~CAP_NET_ADMIN CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_MODULE" ];
+            MemoryDenyWriteExecute = true;
+            ProtectKernelModules = true;
+            ProtectKernelTunables = true;
+            ProtectControlGroups = true;
+            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ];
+            RestrictNamespaces = true;
+            RestrictRealtime = true;
           };
         };
 
diff --git a/nixos/modules/services/mail/public-inbox.nix b/nixos/modules/services/mail/public-inbox.nix
index 4944d46fbd736..bab4e8bb8d107 100644
--- a/nixos/modules/services/mail/public-inbox.nix
+++ b/nixos/modules/services/mail/public-inbox.nix
@@ -144,12 +144,7 @@ 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.";
-    };
+    package = mkPackageOption pkgs "public-inbox" { };
     path = mkOption {
       type = with types; listOf package;
       default = [];
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 22a4e3c451ab1..3f1a695ab91ae 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -29,19 +29,8 @@ in
       description = lib.mdDoc "Hostname to use for the nginx vhost";
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.roundcube;
-      defaultText = literalExpression "pkgs.roundcube";
-
-      example = literalExpression ''
-        roundcube.withPlugins (plugins: [ plugins.persistent_login ])
-      '';
-
-      description = lib.mdDoc ''
-        The package which contains roundcube's sources. Can be overridden to create
-        an environment which contains roundcube and third-party plugins.
-      '';
+    package = mkPackageOption pkgs "roundcube" {
+      example = "roundcube.withPlugins (plugins: [ plugins.persistent_login ])";
     };
 
     database = {
@@ -113,6 +102,12 @@ in
       apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M";
     };
 
+    configureNginx = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc "Configure nginx as a reverse proxy for roundcube.";
+    };
+
     extraConfig = mkOption {
       type = types.lines;
       default = "";
@@ -131,7 +126,7 @@ in
       ${lib.optionalString (!localDB) ''
         $password = file('${cfg.database.passwordFile}')[0];
         $password = preg_split('~\\\\.(*SKIP)(*FAIL)|\:~s', $password);
-        $password = end($password);
+        $password = rtrim(end($password));
         $password = str_replace("\\:", ":", $password);
         $password = str_replace("\\\\", "\\", $password);
       ''}
@@ -153,40 +148,61 @@ in
       ${cfg.extraConfig}
     '';
 
-    services.nginx = {
+    services.nginx = lib.mkIf cfg.configureNginx {
       enable = true;
       virtualHosts = {
         ${cfg.hostName} = {
           forceSSL = mkDefault true;
           enableACME = mkDefault true;
+          root = cfg.package;
           locations."/" = {
-            root = cfg.package;
             index = "index.php";
+            priority = 1100;
             extraConfig = ''
-              location ~* \.php(/|$) {
-                fastcgi_split_path_info ^(.+\.php)(/.+)$;
-                fastcgi_pass unix:${fpm.socket};
-
-                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-                fastcgi_param PATH_INFO       $fastcgi_path_info;
-
-                include ${config.services.nginx.package}/conf/fastcgi_params;
-                include ${pkgs.nginx}/conf/fastcgi.conf;
-              }
+              add_header Cache-Control 'public, max-age=604800, must-revalidate';
+            '';
+          };
+          locations."~ ^/(SQL|bin|config|logs|temp|vendor)/" = {
+            priority = 3110;
+            extraConfig = ''
+              return 404;
+            '';
+          };
+          locations."~ ^/(CHANGELOG.md|INSTALL|LICENSE|README.md|SECURITY.md|UPGRADING|composer.json|composer.lock)" = {
+            priority = 3120;
+            extraConfig = ''
+              return 404;
+            '';
+          };
+          locations."~* \\.php(/|$)" = {
+            priority = 3130;
+            extraConfig = ''
+              fastcgi_pass unix:${fpm.socket};
+              fastcgi_param PATH_INFO $fastcgi_path_info;
+              fastcgi_split_path_info ^(.+\.php)(/.+)$;
+              include ${config.services.nginx.package}/conf/fastcgi.conf;
             '';
           };
         };
       };
     };
 
+    assertions = [
+      {
+        assertion = localDB -> cfg.database.username == cfg.database.dbname;
+        message = ''
+          When setting up a DB and its owner user, the owner and the DB name must be
+          equal!
+        '';
+      }
+    ];
+
     services.postgresql = mkIf localDB {
       enable = true;
       ensureDatabases = [ cfg.database.dbname ];
       ensureUsers = [ {
         name = cfg.database.username;
-        ensurePermissions = {
-          "DATABASE ${cfg.database.username}" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       } ];
     };
 
@@ -234,6 +250,7 @@ in
         path = [ config.services.postgresql.package ];
       })
       {
+        wants = [ "network-online.target" ];
         after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         script = let
diff --git a/nixos/modules/services/mail/rspamd-trainer.nix b/nixos/modules/services/mail/rspamd-trainer.nix
new file mode 100644
index 0000000000000..bb78ddf9dd471
--- /dev/null
+++ b/nixos/modules/services/mail/rspamd-trainer.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.rspamd-trainer;
+  format = pkgs.formats.toml { };
+
+in {
+  options.services.rspamd-trainer = {
+
+    enable = mkEnableOption (mdDoc "Spam/ham trainer for rspamd");
+
+    settings = mkOption {
+      default = { };
+      description = mdDoc ''
+        IMAP authentication configuration for rspamd-trainer. For supplying
+        the IMAP password, use the `secrets` option.
+      '';
+      type = types.submodule {
+        freeformType = format.type;
+      };
+      example = literalExpression ''
+        {
+          HOST = "localhost";
+          USERNAME = "spam@example.com";
+          INBOXPREFIX = "INBOX/";
+        }
+      '';
+    };
+
+    secrets = lib.mkOption {
+      type = with types; listOf path;
+      description = lib.mdDoc ''
+        A list of files containing the various secrets. Should be in the
+        format expected by systemd's `EnvironmentFile` directory. For the
+        IMAP account password use `PASSWORD = mypassword`.
+      '';
+      default = [ ];
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      services.rspamd-trainer = {
+        description = "Spam/ham trainer for rspamd";
+        serviceConfig = {
+          ExecStart = "${pkgs.rspamd-trainer}/bin/rspamd-trainer";
+          WorkingDirectory = "/var/lib/rspamd-trainer";
+          StateDirectory = [ "rspamd-trainer/log" ];
+          Type = "oneshot";
+          DynamicUser = true;
+          EnvironmentFile = [
+            ( format.generate "rspamd-trainer-env" cfg.settings )
+            cfg.secrets
+          ];
+        };
+      };
+      timers."rspamd-trainer" = {
+        wantedBy = [ "timers.target" ];
+        timerConfig = {
+          OnBootSec = "10m";
+          OnUnitActiveSec = "10m";
+          Unit = "rspamd-trainer.service";
+        };
+      };
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ onny ];
+
+}
diff --git a/nixos/modules/services/mail/stalwart-mail.nix b/nixos/modules/services/mail/stalwart-mail.nix
index fdbdc99070b93..f576a426b318f 100644
--- a/nixos/modules/services/mail/stalwart-mail.nix
+++ b/nixos/modules/services/mail/stalwart-mail.nix
@@ -11,14 +11,14 @@ let
 in {
   options.services.stalwart-mail = {
     enable = mkEnableOption (mdDoc "the Stalwart all-in-one email server");
-    package = mkPackageOptionMD pkgs "stalwart-mail" { };
+    package = mkPackageOption pkgs "stalwart-mail" { };
 
     settings = mkOption {
       inherit (configFormat) type;
       default = { };
       description = mdDoc ''
         Configuration options for the Stalwart email server.
-        See <https://stalw.art/docs/> for available options.
+        See <https://stalw.art/docs/category/configuration> for available options.
 
         By default, the module is configured to store everything locally.
       '';
diff --git a/nixos/modules/services/mail/sympa.nix b/nixos/modules/services/mail/sympa.nix
index 7a5047b2bea54..13fc8656a2b5a 100644
--- a/nixos/modules/services/mail/sympa.nix
+++ b/nixos/modules/services/mail/sympa.nix
@@ -218,7 +218,7 @@ in
         default = null;
         example = "/run/keys/sympa-dbpassword";
         description = lib.mdDoc ''
-          A file containing the password for {option}`services.sympa.database.user`.
+          A file containing the password for {option}`services.sympa.database.name`.
         '';
       };
 
@@ -342,6 +342,7 @@ in
 
       db_type = cfg.database.type;
       db_name = cfg.database.name;
+      db_user = cfg.database.name;
     }
     // (optionalAttrs (cfg.database.host != null) {
       db_host = cfg.database.host;
@@ -355,9 +356,6 @@ in
     // (optionalAttrs (cfg.database.port != null) {
       db_port = cfg.database.port;
     })
-    // (optionalAttrs (cfg.database.user != null) {
-      db_user = cfg.database.user;
-    })
     // (optionalAttrs (cfg.mta.type == "postfix") {
       sendmail_aliases = "${dataDir}/sympa_transport";
       aliases_program  = "${pkgs.postfix}/bin/postmap";
@@ -393,7 +391,7 @@ in
     users.groups.${group} = {};
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.sympa.database.user must be set to ${user} if services.sympa.database.createLocally is set to true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -437,7 +435,7 @@ in
 
       wantedBy = [ "multi-user.target" ];
       after = [ "network-online.target" ];
-      wants = sympaSubServices;
+      wants = sympaSubServices ++ [ "network-online.target" ];
       before = sympaSubServices;
       serviceConfig = sympaServiceConfig "sympa_msg";
 
@@ -579,7 +577,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/matrix/appservice-discord.nix b/nixos/modules/services/matrix/appservice-discord.nix
index f579c2529c0a5..c2c3abb79f97e 100644
--- a/nixos/modules/services/matrix/appservice-discord.nix
+++ b/nixos/modules/services/matrix/appservice-discord.nix
@@ -15,14 +15,7 @@ in {
     services.matrix-appservice-discord = {
       enable = mkEnableOption (lib.mdDoc "a bridge between Matrix and Discord");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.matrix-appservice-discord;
-        defaultText = literalExpression "pkgs.matrix-appservice-discord";
-        description = lib.mdDoc ''
-          Which package of matrix-appservice-discord to use.
-        '';
-      };
+      package = mkPackageOption pkgs "matrix-appservice-discord" { };
 
       settings = mkOption rec {
         # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
@@ -100,9 +93,9 @@ in {
 
       serviceDependencies = mkOption {
         type = with types; listOf str;
-        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        default = optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
         defaultText = literalExpression ''
-          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+          optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
         '';
         description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service,
diff --git a/nixos/modules/services/matrix/appservice-irc.nix b/nixos/modules/services/matrix/appservice-irc.nix
index d153ffc2ace87..c79cd799b4d0e 100644
--- a/nixos/modules/services/matrix/appservice-irc.nix
+++ b/nixos/modules/services/matrix/appservice-irc.nix
@@ -214,7 +214,7 @@ in {
         RestrictRealtime = true;
         PrivateMounts = true;
         SystemCallFilter = [
-          "@system-service @pkey"
+          "@system-service @pkey @chown"
           "~@privileged @resources"
         ];
         SystemCallArchitectures = "native";
diff --git a/nixos/modules/services/matrix/conduit.nix b/nixos/modules/services/matrix/conduit.nix
index 76af7ba228579..b0fc85dbda7b0 100644
--- a/nixos/modules/services/matrix/conduit.nix
+++ b/nixos/modules/services/matrix/conduit.nix
@@ -20,14 +20,7 @@ in
         example = { RUST_BACKTRACE="yes"; };
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.matrix-conduit;
-        defaultText = lib.literalExpression "pkgs.matrix-conduit";
-        description = lib.mdDoc ''
-          Package of the conduit matrix server to use.
-        '';
-      };
+      package = mkPackageOption pkgs "matrix-conduit" { };
 
       settings = mkOption {
         type = types.submodule {
diff --git a/nixos/modules/services/matrix/matrix-sliding-sync.nix b/nixos/modules/services/matrix/matrix-sliding-sync.nix
index 9bf4de3317cc2..8b22cd7dba802 100644
--- a/nixos/modules/services/matrix/matrix-sliding-sync.nix
+++ b/nixos/modules/services/matrix/matrix-sliding-sync.nix
@@ -1,10 +1,14 @@
 { config, lib, pkgs, ... }:
 
 let
-  cfg = config.services.matrix-synapse.sliding-sync;
+  cfg = config.services.matrix-sliding-sync;
 in
 {
-  options.services.matrix-synapse.sliding-sync = {
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "matrix-synapse" "sliding-sync" ] [ "services" "matrix-sliding-sync" ])
+  ];
+
+  options.services.matrix-sliding-sync = {
     enable = lib.mkEnableOption (lib.mdDoc "sliding sync");
 
     package = lib.mkPackageOption pkgs "matrix-sliding-sync" { };
@@ -44,7 +48,7 @@ in
         };
       };
       default = { };
-      description = ''
+      description = lib.mdDoc ''
         Freeform environment variables passed to the sliding sync proxy.
         Refer to <https://github.com/matrix-org/sliding-sync#setup> for all supported values.
       '';
@@ -74,14 +78,18 @@ in
     services.postgresql = lib.optionalAttrs cfg.createDatabase {
       enable = true;
       ensureDatabases = [ "matrix-sliding-sync" ];
-      ensureUsers = [ rec {
+      ensureUsers = [ {
         name = "matrix-sliding-sync";
-        ensurePermissions."DATABASE \"${name}\"" = "ALL PRIVILEGES";
+        ensureDBOwnership = true;
       } ];
     };
 
-    systemd.services.matrix-sliding-sync = {
-      after = lib.optional cfg.createDatabase "postgresql.service";
+    systemd.services.matrix-sliding-sync = rec {
+      after =
+        lib.optional cfg.createDatabase "postgresql.service"
+        ++ lib.optional config.services.dendrite.enable "dendrite.service"
+        ++ lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+      wants = after;
       wantedBy = [ "multi-user.target" ];
       environment = cfg.settings;
       serviceConfig = {
@@ -90,6 +98,8 @@ in
         ExecStart = lib.getExe cfg.package;
         StateDirectory = "matrix-sliding-sync";
         WorkingDirectory = "%S/matrix-sliding-sync";
+        Restart = "on-failure";
+        RestartSec = "1s";
       };
     };
   };
diff --git a/nixos/modules/services/matrix/maubot.md b/nixos/modules/services/matrix/maubot.md
new file mode 100644
index 0000000000000..f6a05db56cafd
--- /dev/null
+++ b/nixos/modules/services/matrix/maubot.md
@@ -0,0 +1,103 @@
+# Maubot {#module-services-maubot}
+
+[Maubot](https://github.com/maubot/maubot) is a plugin-based bot
+framework for Matrix.
+
+## Configuration {#module-services-maubot-configuration}
+
+1. Set [](#opt-services.maubot.enable) to `true`. The service will use
+   SQLite by default.
+2. If you want to use PostgreSQL instead of SQLite, do this:
+
+   ```nix
+   services.maubot.settings.database = "postgresql://maubot@localhost/maubot";
+   ```
+
+   If the PostgreSQL connection requires a password, you will have to
+   add it later on step 8.
+3. If you plan to expose your Maubot interface to the web, do something
+   like this:
+   ```nix
+   services.nginx.virtualHosts."matrix.example.org".locations = {
+     "/_matrix/maubot/" = {
+       proxyPass = "http://127.0.0.1:${toString config.services.maubot.settings.server.port}";
+       proxyWebsockets = true;
+     };
+   };
+   services.maubot.settings.server.public_url = "matrix.example.org";
+   # do the following only if you want to use something other than /_matrix/maubot...
+   services.maubot.settings.server.ui_base_path = "/another/base/path";
+   ```
+4. Optionally, set `services.maubot.pythonPackages` to a list of python3
+   packages to make available for Maubot plugins.
+5. Optionally, set `services.maubot.plugins` to a list of Maubot
+   plugins (full list available at https://plugins.maubot.xyz/):
+   ```nix
+   services.maubot.plugins = with config.services.maubot.package.plugins; [
+     reactbot
+     # This will only change the default config! After you create a
+     # plugin instance, the default config will be copied into that
+     # instance's config in Maubot's database, and further base config
+     # changes won't affect the running plugin.
+     (rss.override {
+       base_config = {
+         update_interval = 60;
+         max_backoff = 7200;
+         spam_sleep = 2;
+         command_prefix = "rss";
+         admins = [ "@chayleaf:pavluk.org" ];
+       };
+     })
+   ];
+   # ...or...
+   services.maubot.plugins = config.services.maubot.package.plugins.allOfficialPlugins;
+   # ...or...
+   services.maubot.plugins = config.services.maubot.package.plugins.allPlugins;
+   # ...or...
+   services.maubot.plugins = with config.services.maubot.package.plugins; [
+     (weather.override {
+       # you can pass base_config as a string
+       base_config = ''
+         default_location: New York
+         default_units: M
+         default_language:
+         show_link: true
+         show_image: false
+       '';
+     })
+   ];
+   ```
+6. Start Maubot at least once before doing the following steps (it's
+   necessary to generate the initial config).
+7. If your PostgreSQL connection requires a password, add
+   `database: postgresql://user:password@localhost/maubot`
+   to `/var/lib/maubot/config.yaml`. This overrides the Nix-provided
+   config. Even then, don't remove the `database` line from Nix config
+   so the module knows you use PostgreSQL!
+8. To create a user account for logging into Maubot web UI and
+   configuring it, generate a password using the shell command
+   `mkpasswd -R 12 -m bcrypt`, and edit `/var/lib/maubot/config.yaml`
+   with the following:
+
+   ```yaml
+   admins:
+       admin_username: $2b$12$g.oIStUeUCvI58ebYoVMtO/vb9QZJo81PsmVOomHiNCFbh0dJpZVa
+   ```
+
+   Where `admin_username` is your username, and `$2b...` is the bcrypted
+   password.
+9. Optional: if you want to be able to register new users with the
+   Maubot CLI (`mbc`), and your homeserver is private, add your
+   homeserver's registration key to `/var/lib/maubot/config.yaml`:
+
+   ```yaml
+   homeservers:
+       matrix.example.org:
+           url: https://matrix.example.org
+           secret: your-very-secret-key
+   ```
+10. Restart Maubot after editing `/var/lib/maubot/config.yaml`,and
+    Maubot will be available at
+    `https://matrix.example.org/_matrix/maubot`. If you want to use the
+    `mbc` CLI, it's available using the `maubot` package (`nix-shell -p
+    maubot`).
diff --git a/nixos/modules/services/matrix/maubot.nix b/nixos/modules/services/matrix/maubot.nix
new file mode 100644
index 0000000000000..bc96ca03b1fc7
--- /dev/null
+++ b/nixos/modules/services/matrix/maubot.nix
@@ -0,0 +1,459 @@
+{ lib
+, config
+, pkgs
+, ...
+}:
+
+let
+  cfg = config.services.maubot;
+
+  wrapper1 =
+    if cfg.plugins == [ ]
+    then cfg.package
+    else cfg.package.withPlugins (_: cfg.plugins);
+
+  wrapper2 =
+    if cfg.pythonPackages == [ ]
+    then wrapper1
+    else wrapper1.withPythonPackages (_: cfg.pythonPackages);
+
+  settings = lib.recursiveUpdate cfg.settings {
+    plugin_directories.trash =
+      if cfg.settings.plugin_directories.trash == null
+      then "delete"
+      else cfg.settings.plugin_directories.trash;
+    server.unshared_secret = "generate";
+  };
+
+  finalPackage = wrapper2.withBaseConfig settings;
+
+  isPostgresql = db: builtins.isString db && lib.hasPrefix "postgresql://" db;
+  isLocalPostgresDB = db: isPostgresql db && builtins.any (x: lib.hasInfix x db) [
+    "@127.0.0.1/"
+    "@::1/"
+    "@[::1]/"
+    "@localhost/"
+  ];
+  parsePostgresDB = db:
+    let
+      noSchema = lib.removePrefix "postgresql://" db;
+    in {
+      username = builtins.head (lib.splitString "@" noSchema);
+      database = lib.last (lib.splitString "/" noSchema);
+    };
+
+  postgresDBs = builtins.filter isPostgresql [
+    cfg.settings.database
+    cfg.settings.crypto_database
+    cfg.settings.plugin_databases.postgres
+  ];
+
+  localPostgresDBs = builtins.filter isLocalPostgresDB postgresDBs;
+
+  parsedLocalPostgresDBs = map parsePostgresDB localPostgresDBs;
+  parsedPostgresDBs = map parsePostgresDB postgresDBs;
+
+  hasLocalPostgresDB = localPostgresDBs != [ ];
+in
+{
+  options.services.maubot = with lib; {
+    enable = mkEnableOption (mdDoc "maubot");
+
+    package = lib.mkPackageOption pkgs "maubot" { };
+
+    plugins = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      example = literalExpression ''
+        with config.services.maubot.package.plugins; [
+          xyz.maubot.reactbot
+          xyz.maubot.rss
+        ];
+      '';
+      description = mdDoc ''
+        List of additional maubot plugins to make available.
+      '';
+    };
+
+    pythonPackages = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      example = literalExpression ''
+        with pkgs.python3Packages; [
+          aiohttp
+        ];
+      '';
+      description = mdDoc ''
+        List of additional Python packages to make available for maubot.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/maubot";
+      description = mdDoc ''
+        The directory where maubot stores its stateful data.
+      '';
+    };
+
+    extraConfigFile = mkOption {
+      type = types.str;
+      default = "./config.yaml";
+      defaultText = literalExpression ''"''${config.services.maubot.dataDir}/config.yaml"'';
+      description = mdDoc ''
+        A file for storing secrets. You can pass homeserver registration keys here.
+        If it already exists, **it must contain `server.unshared_secret`** which is used for signing API keys.
+        If `configMutable` is not set to true, **maubot user must have write access to this file**.
+      '';
+    };
+
+    configMutable = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Whether maubot should write updated config into `extraConfigFile`. **This will make your Nix module settings have no effect besides the initial config, as extraConfigFile takes precedence over NixOS settings!**
+      '';
+    };
+
+    settings = mkOption {
+      default = { };
+      description = mdDoc ''
+        YAML settings for maubot. See the
+        [example configuration](https://github.com/maubot/maubot/blob/master/maubot/example-config.yaml)
+        for more info.
+
+        Secrets should be passed in by using `extraConfigFile`.
+      '';
+      type = with types; submodule {
+        options = {
+          database = mkOption {
+            type = str;
+            default = "sqlite:maubot.db";
+            example = "postgresql://username:password@hostname/dbname";
+            description = mdDoc ''
+              The full URI to the database. SQLite and Postgres are fully supported.
+              Other DBMSes supported by SQLAlchemy may or may not work.
+            '';
+          };
+
+          crypto_database = mkOption {
+            type = str;
+            default = "default";
+            example = "postgresql://username:password@hostname/dbname";
+            description = mdDoc ''
+              Separate database URL for the crypto database. By default, the regular database is also used for crypto.
+            '';
+          };
+
+          database_opts = mkOption {
+            type = types.attrs;
+            default = { };
+            description = mdDoc ''
+              Additional arguments for asyncpg.create_pool() or sqlite3.connect()
+            '';
+          };
+
+          plugin_directories = mkOption {
+            default = { };
+            description = mdDoc "Plugin directory paths";
+            type = submodule {
+              options = {
+                upload = mkOption {
+                  type = types.str;
+                  default = "./plugins";
+                  defaultText = literalExpression ''"''${config.services.maubot.dataDir}/plugins"'';
+                  description = mdDoc ''
+                    The directory where uploaded new plugins should be stored.
+                  '';
+                };
+                load = mkOption {
+                  type = types.listOf types.str;
+                  default = [ "./plugins" ];
+                  defaultText = literalExpression ''[ "''${config.services.maubot.dataDir}/plugins" ]'';
+                  description = mdDoc ''
+                    The directories from which plugins should be loaded. Duplicate plugin IDs will be moved to the trash.
+                  '';
+                };
+                trash = mkOption {
+                  type = with types; nullOr str;
+                  default = "./trash";
+                  defaultText = literalExpression ''"''${config.services.maubot.dataDir}/trash"'';
+                  description = mdDoc ''
+                    The directory where old plugin versions and conflicting plugins should be moved. Set to null to delete files immediately.
+                  '';
+                };
+              };
+            };
+          };
+
+          plugin_databases = mkOption {
+            description = mdDoc "Plugin database settings";
+            default = { };
+            type = submodule {
+              options = {
+                sqlite = mkOption {
+                  type = types.str;
+                  default = "./plugins";
+                  defaultText = literalExpression ''"''${config.services.maubot.dataDir}/plugins"'';
+                  description = mdDoc ''
+                    The directory where SQLite plugin databases should be stored.
+                  '';
+                };
+
+                postgres = mkOption {
+                  type = types.nullOr types.str;
+                  default = if isPostgresql cfg.settings.database then "default" else null;
+                  defaultText = literalExpression ''if isPostgresql config.services.maubot.settings.database then "default" else null'';
+                  description = mdDoc ''
+                    The connection URL for plugin database. See [example config](https://github.com/maubot/maubot/blob/master/maubot/example-config.yaml) for exact format.
+                  '';
+                };
+
+                postgres_max_conns_per_plugin = mkOption {
+                  type = types.nullOr types.int;
+                  default = 3;
+                  description = mdDoc ''
+                    Maximum number of connections per plugin instance.
+                  '';
+                };
+
+                postgres_opts = mkOption {
+                  type = types.attrs;
+                  default = { };
+                  description = mdDoc ''
+                    Overrides for the default database_opts when using a non-default postgres connection URL.
+                  '';
+                };
+              };
+            };
+          };
+
+          server = mkOption {
+            default = { };
+            description = mdDoc "Listener config";
+            type = submodule {
+              options = {
+                hostname = mkOption {
+                  type = types.str;
+                  default = "127.0.0.1";
+                  description = mdDoc ''
+                    The IP to listen on
+                  '';
+                };
+                port = mkOption {
+                  type = types.port;
+                  default = 29316;
+                  description = mdDoc ''
+                    The port to listen on
+                  '';
+                };
+                public_url = mkOption {
+                  type = types.str;
+                  default = "http://${cfg.settings.server.hostname}:${toString cfg.settings.server.port}";
+                  defaultText = literalExpression ''"http://''${config.services.maubot.settings.server.hostname}:''${toString config.services.maubot.settings.server.port}"'';
+                  description = mdDoc ''
+                    Public base URL where the server is visible.
+                  '';
+                };
+                ui_base_path = mkOption {
+                  type = types.str;
+                  default = "/_matrix/maubot";
+                  description = mdDoc ''
+                    The base path for the UI.
+                  '';
+                };
+                plugin_base_path = mkOption {
+                  type = types.str;
+                  default = "${config.services.maubot.settings.server.ui_base_path}/plugin/";
+                  defaultText = literalExpression ''
+                    "''${config.services.maubot.settings.server.ui_base_path}/plugin/"
+                  '';
+                  description = mdDoc ''
+                    The base path for plugin endpoints. The instance ID will be appended directly.
+                  '';
+                };
+                override_resource_path = mkOption {
+                  type = types.nullOr types.str;
+                  default = null;
+                  description = mdDoc ''
+                    Override path from where to load UI resources.
+                  '';
+                };
+              };
+            };
+          };
+
+          homeservers = mkOption {
+            type = types.attrsOf (types.submodule {
+              options = {
+                url = mkOption {
+                  type = types.str;
+                  description = mdDoc ''
+                    Client-server API URL
+                  '';
+                };
+              };
+            });
+            default = {
+              "matrix.org" = {
+                url = "https://matrix-client.matrix.org";
+              };
+            };
+            description = mdDoc ''
+              Known homeservers. This is required for the `mbc auth` command and also allows more convenient access from the management UI.
+              If you want to specify registration secrets, pass this via extraConfigFile instead.
+            '';
+          };
+
+          admins = mkOption {
+            type = types.attrsOf types.str;
+            default = { root = ""; };
+            description = mdDoc ''
+              List of administrator users. Plaintext passwords will be bcrypted on startup. Set empty password
+              to prevent normal login. Root is a special user that can't have a password and will always exist.
+            '';
+          };
+
+          api_features = mkOption {
+            type = types.attrsOf bool;
+            default = {
+              login = true;
+              plugin = true;
+              plugin_upload = true;
+              instance = true;
+              instance_database = true;
+              client = true;
+              client_proxy = true;
+              client_auth = true;
+              dev_open = true;
+              log = true;
+            };
+            description = mdDoc ''
+              API feature switches.
+            '';
+          };
+
+          logging = mkOption {
+            type = types.attrs;
+            description = mdDoc ''
+              Python logging configuration. See [section 16.7.2 of the Python
+              documentation](https://docs.python.org/3.6/library/logging.config.html#configuration-dictionary-schema)
+              for more info.
+            '';
+            default = {
+              version = 1;
+              formatters = {
+                colored = {
+                  "()" = "maubot.lib.color_log.ColorFormatter";
+                  format = "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s";
+                };
+                normal = {
+                  format = "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s";
+                };
+              };
+              handlers = {
+                file = {
+                  class = "logging.handlers.RotatingFileHandler";
+                  formatter = "normal";
+                  filename = "./maubot.log";
+                  maxBytes = 10485760;
+                  backupCount = 10;
+                };
+                console = {
+                  class = "logging.StreamHandler";
+                  formatter = "colored";
+                };
+              };
+              loggers = {
+                maubot = {
+                  level = "DEBUG";
+                };
+                mau = {
+                  level = "DEBUG";
+                };
+                aiohttp = {
+                  level = "INFO";
+                };
+              };
+              root = {
+                level = "DEBUG";
+                handlers = [ "file" "console" ];
+              };
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    warnings = lib.optional (builtins.any (x: x.username != x.database) parsedLocalPostgresDBs) ''
+      The Maubot database username doesn't match the database name! This means the user won't be automatically
+      granted ownership of the database. Consider changing either the username or the database name.
+    '';
+    assertions = [
+      {
+        assertion = builtins.all (x: !lib.hasInfix ":" x.username) parsedPostgresDBs;
+        message = ''
+          Putting database passwords in your Nix config makes them world-readable. To securely put passwords
+          in your Maubot config, change /var/lib/maubot/config.yaml after running Maubot at least once as
+          described in the NixOS manual.
+        '';
+      }
+      {
+        assertion = hasLocalPostgresDB -> config.services.postgresql.enable;
+        message = ''
+          Cannot deploy maubot with a configuration for a local postgresql database and a missing postgresql service.
+        '';
+      }
+    ];
+
+    services.postgresql = lib.mkIf hasLocalPostgresDB {
+      enable = true;
+      ensureDatabases = map (x: x.database) parsedLocalPostgresDBs;
+      ensureUsers = lib.flip map parsedLocalPostgresDBs (x: {
+        name = x.username;
+        ensureDBOwnership = lib.mkIf (x.username == x.database) true;
+      });
+    };
+
+    users.users.maubot = {
+      group = "maubot";
+      home = cfg.dataDir;
+      # otherwise StateDirectory is enough
+      createHome = lib.mkIf (cfg.dataDir != "/var/lib/maubot") true;
+      isSystemUser = true;
+    };
+
+    users.groups.maubot = { };
+
+    systemd.services.maubot = rec {
+      description = "maubot - a plugin-based Matrix bot system written in Python";
+      after = [ "network.target" ] ++ wants ++ lib.optional hasLocalPostgresDB "postgresql.service";
+      # all plugins get automatically disabled if maubot starts before synapse
+      wants = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        if [ ! -f "${cfg.extraConfigFile}" ]; then
+          echo "server:" > "${cfg.extraConfigFile}"
+          echo "    unshared_secret: $(head -c40 /dev/random | base32 | ${pkgs.gawk}/bin/awk '{print tolower($0)}')" > "${cfg.extraConfigFile}"
+          chmod 640 "${cfg.extraConfigFile}"
+        fi
+      '';
+
+      serviceConfig = {
+        ExecStart = "${finalPackage}/bin/maubot --config ${cfg.extraConfigFile}" + lib.optionalString (!cfg.configMutable) " --no-update";
+        User = "maubot";
+        Group = "maubot";
+        Restart = "on-failure";
+        RestartSec = "10s";
+        StateDirectory = lib.mkIf (cfg.dataDir == "/var/lib/maubot") "maubot";
+        WorkingDirectory = cfg.dataDir;
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ chayleaf ];
+  meta.doc = ./maubot.md;
+}
diff --git a/nixos/modules/services/matrix/mautrix-facebook.nix b/nixos/modules/services/matrix/mautrix-facebook.nix
index bab6865496dd9..d7cf024bb8077 100644
--- a/nixos/modules/services/matrix/mautrix-facebook.nix
+++ b/nixos/modules/services/matrix/mautrix-facebook.nix
@@ -135,9 +135,7 @@ in {
       ensureDatabases = ["mautrix-facebook"];
       ensureUsers = [{
         name = "mautrix-facebook";
-        ensurePermissions = {
-          "DATABASE \"mautrix-facebook\"" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       }];
     };
 
@@ -145,7 +143,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       wants = [
         "network-online.target"
-      ] ++ optional config.services.matrix-synapse.enable "matrix-synapse.service"
+      ] ++ optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
         ++ optional cfg.configurePostgresql "postgresql.service";
       after = wants;
 
diff --git a/nixos/modules/services/matrix/mautrix-telegram.nix b/nixos/modules/services/matrix/mautrix-telegram.nix
index 17032ed808e93..168c8bf436acc 100644
--- a/nixos/modules/services/matrix/mautrix-telegram.nix
+++ b/nixos/modules/services/matrix/mautrix-telegram.nix
@@ -122,9 +122,9 @@ in {
 
       serviceDependencies = mkOption {
         type = with types; listOf str;
-        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        default = optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
         defaultText = literalExpression ''
-          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+          optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
         '';
         description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service.
@@ -159,7 +159,6 @@ in {
         if [ ! -f '${registrationFile}' ]; then
           ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
             --generate-registration \
-            --base-config='${pkgs.mautrix-telegram}/${pkgs.mautrix-telegram.pythonModule.sitePackages}/mautrix_telegram/example-config.yaml' \
             --config='${settingsFile}' \
             --registration='${registrationFile}'
         fi
diff --git a/nixos/modules/services/matrix/mautrix-whatsapp.nix b/nixos/modules/services/matrix/mautrix-whatsapp.nix
index c4dc482134956..4b561a4b07a38 100644
--- a/nixos/modules/services/matrix/mautrix-whatsapp.nix
+++ b/nixos/modules/services/matrix/mautrix-whatsapp.nix
@@ -100,9 +100,9 @@ in {
 
     serviceDependencies = lib.mkOption {
       type = with lib.types; listOf str;
-      default = lib.optional config.services.matrix-synapse.enable "matrix-synapse.service";
+      default = lib.optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
       defaultText = lib.literalExpression ''
-        optional config.services.matrix-synapse.enable "matrix-synapse.service"
+        optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnits
       '';
       description = lib.mdDoc ''
         List of Systemd services to require and wait for when starting the application service.
diff --git a/nixos/modules/services/matrix/mjolnir.nix b/nixos/modules/services/matrix/mjolnir.nix
index 0824be663340b..4e9a915c23c7b 100644
--- a/nixos/modules/services/matrix/mjolnir.nix
+++ b/nixos/modules/services/matrix/mjolnir.nix
@@ -96,8 +96,8 @@ in
       type = types.submodule {
         options = {
           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.
+            ignoring the accessToken. 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 {
diff --git a/nixos/modules/services/matrix/mx-puppet-discord.nix b/nixos/modules/services/matrix/mx-puppet-discord.nix
index 36c9f8b122ea2..70828804b556c 100644
--- a/nixos/modules/services/matrix/mx-puppet-discord.nix
+++ b/nixos/modules/services/matrix/mx-puppet-discord.nix
@@ -66,9 +66,9 @@ in {
       };
       serviceDependencies = mkOption {
         type = with types; listOf str;
-        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        default = optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit;
         defaultText = literalExpression ''
-          optional config.services.matrix-synapse.enable "matrix-synapse.service"
+          optional config.services.matrix-synapse.enable config.services.matrix-synapse.serviceUnit
         '';
         description = lib.mdDoc ''
           List of Systemd services to require and wait for when starting the application service.
diff --git a/nixos/modules/services/matrix/synapse-log_config.yaml b/nixos/modules/services/matrix/synapse-log_config.yaml
deleted file mode 100644
index d85bdd1208f9a..0000000000000
--- a/nixos/modules/services/matrix/synapse-log_config.yaml
+++ /dev/null
@@ -1,25 +0,0 @@
-version: 1
-
-# In systemd's journal, loglevel is implicitly stored, so let's omit it
-# from the message text.
-formatters:
-    journal_fmt:
-        format: '%(name)s: [%(request)s] %(message)s'
-
-filters:
-    context:
-        (): synapse.util.logcontext.LoggingContextFilter
-        request: ""
-
-handlers:
-    journal:
-        class: systemd.journal.JournalHandler
-        formatter: journal_fmt
-        filters: [context]
-        SYSLOG_IDENTIFIER: synapse
-
-root:
-    level: INFO
-    handlers: [journal]
-
-disable_existing_loggers: False
diff --git a/nixos/modules/services/matrix/synapse.md b/nixos/modules/services/matrix/synapse.md
index 1d22805b472f6..f270be8c8d781 100644
--- a/nixos/modules/services/matrix/synapse.md
+++ b/nixos/modules/services/matrix/synapse.md
@@ -16,13 +16,13 @@ around Matrix.
 
 ## Synapse Homeserver {#module-services-matrix-synapse}
 
-[Synapse](https://github.com/matrix-org/synapse) is
+[Synapse](https://github.com/element-hq/synapse) is
 the reference homeserver implementation of Matrix from the core development
 team at matrix.org. The following configuration example will set up a
 synapse server for the `example.org` domain, served from
 the host `myhostname.example.org`. For more information,
 please refer to the
-[installation instructions of Synapse](https://matrix-org.github.io/synapse/latest/setup/installation.html) .
+[installation instructions of Synapse](https://element-hq.github.io/synapse/latest/setup/installation.html) .
 ```
 { pkgs, lib, config, ... }:
 let
@@ -31,7 +31,7 @@ let
   clientConfig."m.homeserver".base_url = baseUrl;
   serverConfig."m.server" = "${fqdn}:443";
   mkWellKnown = data: ''
-    add_header Content-Type application/json;
+    default_type application/json;
     add_header Access-Control-Allow-Origin *;
     return 200 '${builtins.toJSON data}';
   '';
@@ -70,7 +70,7 @@ in {
         # the domain (i.e. example.org from @foo:example.org) and the federation port
         # is 8448.
         # Further reference can be found in the docs about delegation under
-        # https://matrix-org.github.io/synapse/latest/delegate.html
+        # https://element-hq.github.io/synapse/latest/delegate.html
         locations."= /.well-known/matrix/server".extraConfig = mkWellKnown serverConfig;
         # This is usually needed for homeserver discovery (from e.g. other Matrix clients).
         # Further reference can be found in the upstream docs at
@@ -169,7 +169,7 @@ in an additional file like this:
 ::: {.note}
 It's also possible to user alternative authentication mechanism such as
 [LDAP (via `matrix-synapse-ldap3`)](https://github.com/matrix-org/matrix-synapse-ldap3)
-or [OpenID](https://matrix-org.github.io/synapse/latest/openid.html).
+or [OpenID](https://element-hq.github.io/synapse/latest/openid.html).
 :::
 
 ## Element (formerly known as Riot) Web Client {#module-services-matrix-element-web}
diff --git a/nixos/modules/services/matrix/synapse.nix b/nixos/modules/services/matrix/synapse.nix
index ef69a8adebb01..4c1c396eac056 100644
--- a/nixos/modules/services/matrix/synapse.nix
+++ b/nixos/modules/services/matrix/synapse.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.services.matrix-synapse;
-  format = pkgs.formats.yaml {};
+  format = pkgs.formats.yaml { };
 
   # remove null values from the final configuration
   finalSettings = lib.filterAttrsRecursive (_: v: v != null) cfg.settings;
@@ -12,28 +12,31 @@ let
 
   usePostgresql = cfg.settings.database.name == "psycopg2";
   hasLocalPostgresDB = let args = cfg.settings.database.args; in
-    usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]));
+    usePostgresql
+    && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]))
+    && config.services.postgresql.enable;
+  hasWorkers = cfg.workers != { };
+
+  listenerSupportsResource = resource: listener:
+    lib.any ({ names, ... }: builtins.elem resource names) listener.resources;
+
+  clientListener = findFirst
+    (listenerSupportsResource "client")
+    null
+    (cfg.settings.listeners
+      ++ concatMap ({ worker_listeners, ... }: worker_listeners) (attrValues cfg.workers));
 
   registerNewMatrixUser =
     let
-      isIpv6 = x: lib.length (lib.splitString ":" x) > 1;
-      listener =
-        lib.findFirst (
-          listener: lib.any (
-            resource: lib.any (
-              name: name == "client"
-            ) resource.names
-          ) listener.resources
-        ) (lib.last cfg.settings.listeners) cfg.settings.listeners;
-        # FIXME: Handle cases with missing client listener properly,
-        # don't rely on lib.last, this will not work.
+      isIpv6 = hasInfix ":";
 
       # add a tail, so that without any bind_addresses we still have a useable address
-      bindAddress = head (listener.bind_addresses ++ [ "127.0.0.1" ]);
-      listenerProtocol = if listener.tls
+      bindAddress = head (clientListener.bind_addresses ++ [ "127.0.0.1" ]);
+      listenerProtocol = if clientListener.tls
         then "https"
         else "http";
     in
+    assert assertMsg (clientListener != null) "No client listener found in synapse or one of its workers";
     pkgs.writeShellScriptBin "matrix-synapse-register_new_matrix_user" ''
       exec ${cfg.package}/bin/register_new_matrix_user \
         $@ \
@@ -43,7 +46,7 @@ let
             "[${bindAddress}]"
           else
             "${bindAddress}"
-        }:${builtins.toString listener.port}/"
+        }:${builtins.toString clientListener.port}/"
     '';
 
   defaultExtras = [
@@ -57,7 +60,6 @@ let
     ++ lib.optional (cfg.settings ? oidc_providers) "oidc"
     ++ lib.optional (cfg.settings ? jwt_config) "jwt"
     ++ lib.optional (cfg.settings ? saml2_config) "saml2"
-    ++ lib.optional (cfg.settings ? opentracing) "opentracing"
     ++ lib.optional (cfg.settings ? redis) "redis"
     ++ lib.optional (cfg.settings ? sentry) "sentry"
     ++ lib.optional (cfg.settings ? user_directory) "user-search"
@@ -68,6 +70,41 @@ let
     extras = wantedExtras;
     inherit (cfg) plugins;
   };
+
+  defaultCommonLogConfig = {
+    version = 1;
+    formatters.journal_fmt.format = "%(name)s: [%(request)s] %(message)s";
+    handlers.journal = {
+      class = "systemd.journal.JournalHandler";
+      formatter = "journal_fmt";
+    };
+    root = {
+      level = "INFO";
+      handlers = [ "journal" ];
+    };
+    disable_existing_loggers = false;
+  };
+
+  defaultCommonLogConfigText = generators.toPretty { } defaultCommonLogConfig;
+
+  logConfigText = logName:
+    lib.literalMD ''
+      Path to a yaml file generated from this Nix expression:
+
+      ```
+      ${generators.toPretty { } (
+        recursiveUpdate defaultCommonLogConfig { handlers.journal.SYSLOG_IDENTIFIER = logName; }
+      )}
+      ```
+    '';
+
+  genLogConfigFile = logName: format.generate
+    "synapse-log-${logName}.yaml"
+    (cfg.log // optionalAttrs (cfg.log?handlers.journal) {
+      handlers.journal = cfg.log.handlers.journal // {
+        SYSLOG_IDENTIFIER = logName;
+      };
+    });
 in {
 
   imports = [
@@ -154,10 +191,123 @@ in {
 
   ];
 
-  options = {
+  options = let
+    listenerType = workerContext: types.submodule {
+      options = {
+        port = mkOption {
+          type = types.port;
+          example = 8448;
+          description = lib.mdDoc ''
+            The port to listen for HTTP(S) requests on.
+          '';
+        };
+
+        bind_addresses = mkOption {
+          type = types.listOf types.str;
+          default = [
+            "::1"
+            "127.0.0.1"
+          ];
+          example = literalExpression ''
+            [
+              "::"
+              "0.0.0.0"
+            ]
+          '';
+          description = lib.mdDoc ''
+            IP addresses to bind the listener to.
+          '';
+        };
+
+        type = mkOption {
+          type = types.enum [
+            "http"
+            "manhole"
+            "metrics"
+            "replication"
+          ];
+          default = "http";
+          example = "metrics";
+          description = lib.mdDoc ''
+            The type of the listener, usually http.
+          '';
+        };
+
+        tls = mkOption {
+          type = types.bool;
+          default = !workerContext;
+          example = false;
+          description = lib.mdDoc ''
+            Whether to enable TLS on the listener socket.
+          '';
+        };
+
+        x_forwarded = mkOption {
+          type = types.bool;
+          default = false;
+          example = true;
+          description = lib.mdDoc ''
+            Use the X-Forwarded-For (XFF) header as the client IP and not the
+            actual client IP.
+          '';
+        };
+
+        resources = mkOption {
+          type = types.listOf (types.submodule {
+            options = {
+              names = mkOption {
+                type = types.listOf (types.enum [
+                  "client"
+                  "consent"
+                  "federation"
+                  "health"
+                  "keys"
+                  "media"
+                  "metrics"
+                  "openid"
+                  "replication"
+                  "static"
+                ]);
+                description = lib.mdDoc ''
+                  List of resources to host on this listener.
+                '';
+                example = [
+                  "client"
+                ];
+              };
+              compress = mkOption {
+                default = false;
+                type = types.bool;
+                description = lib.mdDoc ''
+                  Whether synapse should compress HTTP responses to clients that support it.
+                  This should be disabled if running synapse behind a load balancer
+                  that can do automatic compression.
+                '';
+              };
+            };
+          });
+          description = lib.mdDoc ''
+            List of HTTP resources to serve on this listener.
+          '';
+        };
+      };
+    };
+  in {
     services.matrix-synapse = {
       enable = mkEnableOption (lib.mdDoc "matrix.org synapse");
 
+      serviceUnit = lib.mkOption {
+        type = lib.types.str;
+        readOnly = true;
+        description = lib.mdDoc ''
+          The systemd unit (a service or a target) for other services to depend on if they
+          need to be started after matrix-synapse.
+
+          This option is useful as the actual parent unit for all matrix-synapse processes
+          changes when configuring workers.
+        '';
+      };
+
       configFile = mkOption {
         type = types.path;
         readOnly = true;
@@ -195,7 +345,6 @@ in {
           [
             "cache-memory" # Provide statistics about caching memory consumption
             "jwt"          # JSON Web Token authentication
-            "opentracing"  # End-to-end tracing support using Jaeger
             "oidc"         # OpenID Connect authentication
             "postgres"     # PostgreSQL database backend
             "redis"        # Redis support for the replication stream between worker processes
@@ -250,11 +399,54 @@ in {
         '';
       };
 
+      log = mkOption {
+        type = types.attrsOf format.type;
+        defaultText = literalExpression defaultCommonLogConfigText;
+        description = mdDoc ''
+          Default configuration for the loggers used by `matrix-synapse` and its workers.
+          The defaults are added with the default priority which means that
+          these will be merged with additional declarations. These additional
+          declarations also take precedence over the defaults when declared
+          with at least normal priority. For instance
+          the log-level for synapse and its workers can be changed like this:
+
+          ```nix
+          { lib, ... }: {
+            services.matrix-synapse.log.root.level = "WARNING";
+          }
+          ```
+
+          And another field can be added like this:
+
+          ```nix
+          {
+            services.matrix-synapse.log = {
+              loggers."synapse.http.matrixfederationclient".level = "DEBUG";
+            };
+          }
+          ```
+
+          Additionally, the field `handlers.journal.SYSLOG_IDENTIFIER` will be added to
+          each log config, i.e.
+          * `synapse` for `matrix-synapse.service`
+          * `synapse-<worker name>` for `matrix-synapse-worker-<worker name>.service`
+
+          This is only done if this option has a `handlers.journal` field declared.
+
+          To discard all settings declared by this option for each worker and synapse,
+          `lib.mkForce` can be used.
+
+          To discard all settings declared by this option for a single worker or synapse only,
+          [](#opt-services.matrix-synapse.workers._name_.worker_log_config) or
+          [](#opt-services.matrix-synapse.settings.log_config) can be used.
+        '';
+      };
+
       settings = mkOption {
-        default = {};
+        default = { };
         description = mdDoc ''
           The primary synapse configuration. See the
-          [sample configuration](https://github.com/matrix-org/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_config.yaml)
+          [sample configuration](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_config.yaml)
           for possible values.
 
           Secrets should be passed in by using the `extraConfigFiles` option.
@@ -346,8 +538,8 @@ in {
 
             log_config = mkOption {
               type = types.path;
-              default = ./synapse-log_config.yaml;
-              defaultText = lib.literalExpression "nixos/modules/services/matrix/synapse-log_config.yaml";
+              default = genLogConfigFile "synapse";
+              defaultText = logConfigText "synapse";
               description = lib.mdDoc ''
                 The file that holds the logging configuration.
               '';
@@ -409,120 +601,37 @@ in {
             };
 
             listeners = mkOption {
-              type = types.listOf (types.submodule {
-                options = {
-                  port = mkOption {
-                    type = types.port;
-                    example = 8448;
-                    description = lib.mdDoc ''
-                      The port to listen for HTTP(S) requests on.
-                    '';
-                  };
-
-                  bind_addresses = mkOption {
-                    type = types.listOf types.str;
-                    default = [
-                      "::1"
-                      "127.0.0.1"
-                    ];
-                    example = literalExpression ''
-                    [
-                      "::"
-                      "0.0.0.0"
-                    ]
-                    '';
-                    description = lib.mdDoc ''
-                     IP addresses to bind the listener to.
-                    '';
-                  };
-
-                  type = mkOption {
-                    type = types.enum [
-                      "http"
-                      "manhole"
-                      "metrics"
-                      "replication"
-                    ];
-                    default = "http";
-                    example = "metrics";
-                    description = lib.mdDoc ''
-                      The type of the listener, usually http.
-                    '';
-                  };
-
-                  tls = mkOption {
-                    type = types.bool;
-                    default = true;
-                    example = false;
-                    description = lib.mdDoc ''
-                      Whether to enable TLS on the listener socket.
-                    '';
-                  };
-
-                  x_forwarded = mkOption {
-                    type = types.bool;
-                    default = false;
-                    example = true;
-                    description = lib.mdDoc ''
-                      Use the X-Forwarded-For (XFF) header as the client IP and not the
-                      actual client IP.
-                    '';
-                  };
-
-                  resources = mkOption {
-                    type = types.listOf (types.submodule {
-                      options = {
-                        names = mkOption {
-                          type = types.listOf (types.enum [
-                            "client"
-                            "consent"
-                            "federation"
-                            "keys"
-                            "media"
-                            "metrics"
-                            "openid"
-                            "replication"
-                            "static"
-                          ]);
-                          description = lib.mdDoc ''
-                            List of resources to host on this listener.
-                          '';
-                          example = [
-                            "client"
-                          ];
-                        };
-                        compress = mkOption {
-                          type = types.bool;
-                          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.
-                          '';
-                        };
-                      };
-                    });
-                    description = lib.mdDoc ''
-                      List of HTTP resources to serve on this listener.
-                    '';
-                  };
-                };
-              });
-              default = [ {
+              type = types.listOf (listenerType false);
+              default = [{
                 port = 8008;
                 bind_addresses = [ "127.0.0.1" ];
                 type = "http";
                 tls = false;
                 x_forwarded = true;
-                resources = [ {
+                resources = [{
                   names = [ "client" ];
                   compress = true;
                 } {
                   names = [ "federation" ];
                   compress = false;
-                } ];
-              } ];
+                }];
+              }] ++ lib.optional hasWorkers {
+                port = 9093;
+                bind_addresses = [ "127.0.0.1" ];
+                type = "http";
+                tls = false;
+                x_forwarded = false;
+                resources = [{
+                  names = [ "replication" ];
+                  compress = false;
+                }];
+              };
               description = lib.mdDoc ''
                 List of ports that Synapse should listen on, their purpose and their configuration.
+
+                By default, synapse will be configured for client and federation traffic on port 8008, and
+                for worker replication traffic on port 9093. See [`services.matrix-synapse.workers`](#opt-services.matrix-synapse.workers)
+                for more details.
               '';
             };
 
@@ -534,7 +643,7 @@ in {
               default = if versionAtLeast config.system.stateVersion "18.03"
                 then "psycopg2"
                 else "sqlite3";
-               defaultText = literalExpression ''
+              defaultText = literalExpression ''
                 if versionAtLeast config.system.stateVersion "18.03"
                 then "psycopg2"
                 else "sqlite3"
@@ -551,10 +660,10 @@ in {
                 psycopg2 = "matrix-synapse";
               }.${cfg.settings.database.name};
               defaultText = literalExpression ''
-              {
-                sqlite3 = "''${${options.services.matrix-synapse.dataDir}}/homeserver.db";
-                psycopg2 = "matrix-synapse";
-              }.''${${options.services.matrix-synapse.settings}.database.name};
+                {
+                  sqlite3 = "''${${options.services.matrix-synapse.dataDir}}/homeserver.db";
+                  psycopg2 = "matrix-synapse";
+                }.''${${options.services.matrix-synapse.settings}.database.name};
               '';
               description = lib.mdDoc ''
                 Name of the database when using the psycopg2 backend,
@@ -622,7 +731,7 @@ in {
 
             url_preview_ip_range_whitelist = mkOption {
               type = types.listOf types.str;
-              default = [];
+              default = [ ];
               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.
@@ -630,8 +739,27 @@ in {
             };
 
             url_preview_url_blacklist = mkOption {
-              type = types.listOf types.str;
-              default = [];
+              # FIXME revert to just `listOf (attrsOf str)` after some time(tm).
+              type = types.listOf (
+                types.coercedTo
+                  types.str
+                  (const (throw ''
+                    Setting `config.services.matrix-synapse.settings.url_preview_url_blacklist`
+                    to a list of strings has never worked. Due to a bug, this was the type accepted
+                    by the module, but in practice it broke on runtime and as a result, no URL
+                    preview worked anywhere if this was set.
+
+                    See https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#url_preview_url_blacklist
+                    on how to configure it properly.
+                  ''))
+                  (types.attrsOf types.str));
+              default = [ ];
+              example = literalExpression ''
+                [
+                  { scheme = "http"; } # no http previews
+                  { netloc = "www.acme.com"; path = "/foo"; } # block http(s)://www.acme.com/foo
+                ]
+              '';
               description = lib.mdDoc ''
                 Optional list of URL matches that the URL preview spider is
                 denied from accessing.
@@ -671,7 +799,7 @@ in {
 
             turn_uris = mkOption {
               type = types.listOf types.str;
-              default = [];
+              default = [ ];
               example = [
                 "turn:turn.example.com:3487?transport=udp"
                 "turn:turn.example.com:3487?transport=tcp"
@@ -708,12 +836,12 @@ in {
                   };
                 };
               });
-              default = [ {
+              default = [{
                 server_name = "matrix.org";
                 verify_keys = {
                   "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
                 };
-              } ];
+              }];
               description = lib.mdDoc ''
                 The trusted servers to download signing keys from.
               '';
@@ -727,13 +855,114 @@ in {
               '';
             };
 
+            redis = lib.mkOption {
+              type = types.submodule {
+                freeformType = format.type;
+                options = {
+                  enabled = lib.mkOption {
+                    type = types.bool;
+                    default = false;
+                    description = lib.mdDoc ''
+                      Whether to use redis support
+                    '';
+                  };
+                };
+              };
+              default = { };
+              description = lib.mdDoc ''
+                Redis configuration for synapse.
+
+                See the
+                [upstream documentation](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/usage/configuration/config_documentation.md#redis)
+                for available options.
+              '';
+            };
           };
         };
       };
 
+      workers = lib.mkOption {
+        default = { };
+        description = lib.mdDoc ''
+          Options for configuring workers. Worker support will be enabled if at least one worker is configured here.
+
+          See the [worker documention](https://element-hq.github.io/synapse/latest/workers.html#worker-configuration)
+          for possible options for each worker. Worker-specific options overriding the shared homeserver configuration can be
+          specified here for each worker.
+
+          ::: {.note}
+            Worker support will add a replication listener on port 9093 to the main synapse process using the default
+            value of [`services.matrix-synapse.settings.listeners`](#opt-services.matrix-synapse.settings.listeners) and configure that
+            listener as `services.matrix-synapse.settings.instance_map.main`.
+            If you set either of those options, make sure to configure a replication listener yourself.
+
+            A redis server is required for running workers. A local one can be enabled
+            using [`services.matrix-synapse.configureRedisLocally`](#opt-services.matrix-synapse.configureRedisLocally).
+
+            Workers also require a proper reverse proxy setup to direct incoming requests to the appropriate process. See
+            the [reverse proxy documentation](https://element-hq.github.io/synapse/latest/reverse_proxy.html) for a
+            general reverse proxying setup and
+            the [worker documentation](https://element-hq.github.io/synapse/latest/workers.html#available-worker-applications)
+            for the available endpoints per worker application.
+          :::
+        '';
+        type = types.attrsOf (types.submodule ({name, ...}: {
+          freeformType = format.type;
+          options = {
+            worker_app = lib.mkOption {
+              type = types.enum [
+                "synapse.app.generic_worker"
+                "synapse.app.media_repository"
+              ];
+              description = "Type of this worker";
+              default = "synapse.app.generic_worker";
+            };
+            worker_listeners = lib.mkOption {
+              default = [ ];
+              type = types.listOf (listenerType true);
+              description = lib.mdDoc ''
+                List of ports that this worker should listen on, their purpose and their configuration.
+              '';
+            };
+            worker_log_config = lib.mkOption {
+              type = types.path;
+              default = genLogConfigFile "synapse-${name}";
+              defaultText = logConfigText "synapse-${name}";
+              description = lib.mdDoc ''
+                The file for log configuration.
+
+                See the [python documentation](https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema)
+                for the schema and the [upstream repository](https://github.com/element-hq/synapse/blob/v${pkgs.matrix-synapse-unwrapped.version}/docs/sample_log_config.yaml)
+                for an example.
+              '';
+            };
+          };
+        }));
+        default = { };
+        example = lib.literalExpression ''
+          {
+            "federation_sender" = { };
+            "federation_receiver" = {
+              worker_listeners = [
+                {
+                  type = "http";
+                  port = 8009;
+                  bind_addresses = [ "127.0.0.1" ];
+                  tls = false;
+                  x_forwarded = true;
+                  resources = [{
+                    names = [ "federation" ];
+                  }];
+                }
+              ];
+            };
+          }
+        '';
+      };
+
       extraConfigFiles = mkOption {
         type = types.listOf types.path;
-        default = [];
+        default = [ ];
         description = lib.mdDoc ''
           Extra config files to include.
 
@@ -743,35 +972,76 @@ in {
           NixOps is in use.
         '';
       };
+
+      configureRedisLocally = lib.mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to automatically configure a local redis server for matrix-synapse.
+        '';
+      };
     };
   };
 
   config = mkIf cfg.enable {
     assertions = [
-      { assertion = hasLocalPostgresDB -> config.services.postgresql.enable;
+      {
+        assertion = clientListener != null;
+        message = ''
+          At least one listener which serves the `client` resource via HTTP is required
+          by synapse in `services.matrix-synapse.settings.listeners` or in one of the workers!
+        '';
+      }
+      {
+        assertion = hasWorkers -> cfg.settings.redis.enabled;
         message = ''
-          Cannot deploy matrix-synapse with a configuration for a local postgresql database
-            and a missing postgresql service. Since 20.03 it's mandatory to manually configure the
-            database (please read the thread in https://github.com/NixOS/nixpkgs/pull/80447 for
-            further reference).
-
-            If you
-            - try to deploy a fresh synapse, you need to configure the database yourself. An example
-              for this can be found in <nixpkgs/nixos/tests/matrix/synapse.nix>
-            - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true`
-              to your configuration.
-
-          For further information about this update, please read the release-notes of 20.03 carefully.
+          Workers for matrix-synapse require configuring a redis instance. This can be done
+          automatically by setting `services.matrix-synapse.configureRedisLocally = true`.
+        '';
+      }
+      {
+        assertion =
+          let
+            main = cfg.settings.instance_map.main;
+            listener = lib.findFirst
+              (
+                listener:
+                  listener.port == main.port
+                  && listenerSupportsResource "replication" listener
+                  && (lib.any (bind: bind == main.host || bind == "0.0.0.0" || bind == "::") listener.bind_addresses)
+              )
+              null
+              cfg.settings.listeners;
+          in
+          hasWorkers -> (cfg.settings.instance_map ? main && listener != null);
+        message = ''
+          Workers for matrix-synapse require setting `services.matrix-synapse.settings.instance_map.main`
+          to any listener configured in `services.matrix-synapse.settings.listeners` with a `"replication"`
+          resource.
+
+          This is done by default unless you manually configure either of those settings.
         '';
       }
     ];
 
+    services.matrix-synapse.settings.redis = lib.mkIf cfg.configureRedisLocally {
+      enabled = true;
+      path = config.services.redis.servers.matrix-synapse.unixSocket;
+    };
+    services.matrix-synapse.settings.instance_map.main = lib.mkIf hasWorkers (lib.mkDefault {
+      host = "127.0.0.1";
+      port = 9093;
+    });
+
+    services.matrix-synapse.serviceUnit = if hasWorkers then "matrix-synapse.target" else "matrix-synapse.service";
     services.matrix-synapse.configFile = configFile;
     services.matrix-synapse.package = wrapped;
 
     # default them, so they are additive
     services.matrix-synapse.extras = defaultExtras;
 
+    services.matrix-synapse.log = mapAttrsRecursive (const mkDefault) defaultCommonLogConfig;
+
     users.users.matrix-synapse = {
       group = "matrix-synapse";
       home = cfg.dataDir;
@@ -784,64 +1054,128 @@ in {
       gid = config.ids.gids.matrix-synapse;
     };
 
-    systemd.services.matrix-synapse = {
-      description = "Synapse Matrix homeserver";
-      after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
+    systemd.targets.matrix-synapse = lib.mkIf hasWorkers {
+      description = "Synapse Matrix parent target";
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
       wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        ${cfg.package}/bin/synapse_homeserver \
-          --config-path ${configFile} \
-          --keys-directory ${cfg.dataDir} \
-          --generate-keys
-      '';
-      environment = optionalAttrs (cfg.withJemalloc) {
-        LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
-      };
-      serviceConfig = {
-        Type = "notify";
-        User = "matrix-synapse";
-        Group = "matrix-synapse";
-        WorkingDirectory = cfg.dataDir;
-        ExecStartPre = [ ("+" + (pkgs.writeShellScript "matrix-synapse-fix-permissions" ''
-          chown matrix-synapse:matrix-synapse ${cfg.settings.signing_key_path}
-          chmod 0600 ${cfg.settings.signing_key_path}
-        '')) ];
-        ExecStart = ''
-          ${cfg.package}/bin/synapse_homeserver \
-            ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
-            --keys-directory ${cfg.dataDir}
-        '';
-        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" ];
-      };
+    };
+
+    systemd.services =
+      let
+        targetConfig =
+          if hasWorkers
+          then {
+            partOf = [ "matrix-synapse.target" ];
+            wantedBy = [ "matrix-synapse.target" ];
+            unitConfig.ReloadPropagatedFrom = "matrix-synapse.target";
+            requires = optional hasLocalPostgresDB "postgresql.service";
+          }
+          else {
+            wants = [ "network-online.target" ];
+            after = [ "network-online.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
+            requires = optional hasLocalPostgresDB "postgresql.service";
+            wantedBy = [ "multi-user.target" ];
+          };
+        baseServiceConfig = {
+          environment = optionalAttrs (cfg.withJemalloc) {
+            LD_PRELOAD = "${pkgs.jemalloc}/lib/libjemalloc.so";
+          };
+          serviceConfig = {
+            Type = "notify";
+            User = "matrix-synapse";
+            Group = "matrix-synapse";
+            WorkingDirectory = cfg.dataDir;
+            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 cfg.settings.media_store_path ];
+            RemoveIPC = true;
+            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+            RestrictNamespaces = true;
+            RestrictRealtime = true;
+            RestrictSUIDSGID = true;
+            SystemCallArchitectures = "native";
+            SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ];
+          };
+        }
+        // targetConfig;
+        genWorkerService = name: workerCfg:
+          let
+            finalWorkerCfg = workerCfg // { worker_name = name; };
+            workerConfigFile = format.generate "worker-${name}.yaml" finalWorkerCfg;
+          in
+          {
+            name = "matrix-synapse-worker-${name}";
+            value = lib.mkMerge [
+              baseServiceConfig
+              {
+                description = "Synapse Matrix worker ${name}";
+                # make sure the main process starts first for potential database migrations
+                after = [ "matrix-synapse.service" ];
+                requires = [ "matrix-synapse.service" ];
+                serviceConfig = {
+                  ExecStart = ''
+                    ${cfg.package}/bin/synapse_worker \
+                      ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile workerConfigFile ] ++ cfg.extraConfigFiles) }
+                      --keys-directory ${cfg.dataDir}
+                  '';
+                };
+              }
+            ];
+          };
+      in
+      {
+        matrix-synapse = lib.mkMerge [
+          baseServiceConfig
+          {
+            description = "Synapse Matrix homeserver";
+            preStart = ''
+              ${cfg.package}/bin/synapse_homeserver \
+                --config-path ${configFile} \
+                --keys-directory ${cfg.dataDir} \
+                --generate-keys
+            '';
+            serviceConfig = {
+              ExecStartPre = [
+                ("+" + (pkgs.writeShellScript "matrix-synapse-fix-permissions" ''
+                  chown matrix-synapse:matrix-synapse ${cfg.settings.signing_key_path}
+                  chmod 0600 ${cfg.settings.signing_key_path}
+                ''))
+              ];
+              ExecStart = ''
+                ${cfg.package}/bin/synapse_homeserver \
+                  ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
+                  --keys-directory ${cfg.dataDir}
+              '';
+            };
+          }
+        ];
+      }
+      // (lib.mapAttrs' genWorkerService cfg.workers);
+
+    services.redis.servers.matrix-synapse = lib.mkIf cfg.configureRedisLocally {
+      enable = true;
+      user = "matrix-synapse";
     };
 
     environment.systemPackages = [ registerNewMatrixUser ];
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index b8e9dcaf46631..6ba6ff5ca3cbc 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -85,15 +85,12 @@ in {
         '';
       };
 
-      jre = mkOption {
-        type = types.package;
-        default = pkgs.jre8;
-        defaultText = literalExpression "pkgs.jre8";
-        description = lib.mdDoc ''
-          JRE package to use.
-
+      jre = mkPackageOption pkgs "jre8" {
+        extraDescription = ''
+          ::: {.note}
           Airsonic only supports Java 8, airsonic-advanced requires at least
           Java 11.
+          :::
         '';
       };
 
diff --git a/nixos/modules/services/misc/ssm-agent.nix b/nixos/modules/services/misc/amazon-ssm-agent.nix
index d1f371c2bd617..89a1c07665106 100644
--- a/nixos/modules/services/misc/ssm-agent.nix
+++ b/nixos/modules/services/misc/amazon-ssm-agent.nix
@@ -2,7 +2,7 @@
 
 with lib;
 let
-  cfg = config.services.ssm-agent;
+  cfg = config.services.amazon-ssm-agent;
 
   # The SSM agent doesn't pay attention to our /etc/os-release yet, and the lsb-release tool
   # in nixpkgs doesn't seem to work properly on NixOS, so let's just fake the two fields SSM
@@ -15,48 +15,54 @@ let
       -r) echo "${config.system.nixos.version}";;
     esac
   '';
+
+  sudoRule = {
+    users = [ "ssm-user" ];
+    commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ];
+  };
 in {
-  options.services.ssm-agent = {
-    enable = mkEnableOption (lib.mdDoc "AWS SSM agent");
+  imports = [
+    (mkRenamedOptionModule [ "services" "ssm-agent" "enable" ] [ "services" "amazon-ssm-agent" "enable" ])
+    (mkRenamedOptionModule [ "services" "ssm-agent" "package" ] [ "services" "amazon-ssm-agent" "package" ])
+  ];
+
+  options.services.amazon-ssm-agent = {
+    enable = mkEnableOption (lib.mdDoc "Amazon SSM agent");
 
     package = mkOption {
       type = types.path;
-      description = lib.mdDoc "The SSM agent package to use";
-      default = pkgs.ssm-agent.override { overrideEtc = false; };
-      defaultText = literalExpression "pkgs.ssm-agent.override { overrideEtc = false; }";
+      description = lib.mdDoc "The Amazon SSM agent package to use";
+      default = pkgs.amazon-ssm-agent.override { overrideEtc = false; };
+      defaultText = literalExpression "pkgs.amazon-ssm-agent.override { overrideEtc = false; }";
     };
   };
 
   config = mkIf cfg.enable {
-    systemd.services.ssm-agent = {
+    # See https://github.com/aws/amazon-ssm-agent/blob/mainline/packaging/linux/amazon-ssm-agent.service
+    systemd.services.amazon-ssm-agent = {
       inherit (cfg.package.meta) description;
-      after    = [ "network.target" ];
+      wants    = [ "network-online.target" ];
+      after    = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
 
       path = [ fake-lsb-release pkgs.coreutils ];
+
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/amazon-ssm-agent";
         KillMode = "process";
         # We want this restating pretty frequently. It could be our only means
         # of accessing the instance.
         Restart = "always";
-        RestartSec = "1min";
+        RestartPreventExitStatus = 194;
+        RestartSec = "90";
       };
     };
 
     # Add user that Session Manager needs, and give it sudo.
     # This is consistent with Amazon Linux 2 images.
-    security.sudo.extraRules = [
-      {
-        users = [ "ssm-user" ];
-        commands = [
-          {
-            command = "ALL";
-            options = [ "NOPASSWD" ];
-          }
-        ];
-      }
-    ];
+    security.sudo.extraRules = [ sudoRule ];
+    security.sudo-rs.extraRules = [ sudoRule ];
+
     # On Amazon Linux 2 images, the ssm-user user is pretty much a
     # normal user with its own group. We do the same.
     users.groups.ssm-user = {};
@@ -65,7 +71,7 @@ in {
       group = "ssm-user";
     };
 
-    environment.etc."amazon/ssm/seelog.xml".source = "${cfg.package}/seelog.xml.template";
+    environment.etc."amazon/ssm/seelog.xml".source = "${cfg.package}/etc/amazon/ssm/seelog.xml.template";
 
     environment.etc."amazon/ssm/amazon-ssm-agent.json".source =  "${cfg.package}/etc/amazon/ssm/amazon-ssm-agent.json.template";
 
diff --git a/nixos/modules/services/misc/ananicy.nix b/nixos/modules/services/misc/ananicy.nix
index bc1b28efc0baf..01e1053c9e0e2 100644
--- a/nixos/modules/services/misc/ananicy.nix
+++ b/nixos/modules/services/misc/ananicy.nix
@@ -15,21 +15,13 @@ in
     services.ananicy = {
       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 = lib.mdDoc ''
-          Which ananicy package to use.
-        '';
+      package = mkPackageOption pkgs "ananicy" {
+        example = "ananicy-cpp";
       };
 
-      rulesProvider = mkOption {
-        type = types.package;
-        default = pkgs.ananicy;
-        defaultText = literalExpression "pkgs.ananicy";
-        example = literalExpression "pkgs.ananicy-cpp";
+      rulesProvider = mkPackageOption pkgs "ananicy" {
+        example = "ananicy-cpp";
+      } // {
         description = lib.mdDoc ''
           Which package to copy default rules,types,cgroups from.
         '';
diff --git a/nixos/modules/services/misc/anki-sync-server.md b/nixos/modules/services/misc/anki-sync-server.md
new file mode 100644
index 0000000000000..5d2b4da4d2fc2
--- /dev/null
+++ b/nixos/modules/services/misc/anki-sync-server.md
@@ -0,0 +1,68 @@
+# Anki Sync Server {#module-services-anki-sync-server}
+
+[Anki Sync Server](https://docs.ankiweb.net/sync-server.html) is the built-in
+sync server, present in recent versions of Anki. Advanced users who cannot or
+do not wish to use AnkiWeb can use this sync server instead of AnkiWeb.
+
+This module is compatible only with Anki versions >=2.1.66, due to [recent
+enhancements to the Nix anki
+package](https://github.com/NixOS/nixpkgs/commit/05727304f8815825565c944d012f20a9a096838a).
+
+## Basic Usage {#module-services-anki-sync-server-basic-usage}
+
+By default, the module creates a
+[`systemd`](https://www.freedesktop.org/wiki/Software/systemd/)
+unit which runs the sync server with an isolated user using the systemd
+`DynamicUser` option.
+
+This can be done by enabling the `anki-sync-server` service:
+```
+{ ... }:
+
+{
+  services.anki-sync-server.enable = true;
+}
+```
+
+It is necessary to set at least one username-password pair under
+{option}`services.anki-sync-server.users`. For example
+
+```
+{
+  services.anki-sync-server.users = [
+    {
+      username = "user";
+      passwordFile = /etc/anki-sync-server/user;
+    }
+  ];
+}
+```
+
+Here, `passwordFile` is the path to a file containing just the password in
+plaintext. Make sure to set permissions to make this file unreadable to any
+user besides root.
+
+By default, the server listen address {option}`services.anki-sync-server.host`
+is set to localhost, listening on port
+{option}`services.anki-sync-server.port`, and does not open the firewall. This
+is suitable for purely local testing, or to be used behind a reverse proxy. If
+you want to expose the sync server directly to other computers (not recommended
+in most circumstances, because the sync server doesn't use HTTPS), then set the
+following options:
+
+```
+{
+  services.anki-sync-server.host = "0.0.0.0";
+  services.anki-sync-server.openFirewall = true;
+}
+```
+
+
+## Alternatives {#module-services-anki-sync-server-alternatives}
+
+The [`ankisyncd` NixOS
+module](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/misc/ankisyncd.nix)
+provides similar functionality, but using a third-party implementation,
+[`anki-sync-server-rs`](https://github.com/ankicommunity/anki-sync-server-rs/).
+According to that project's README, it is "no longer maintained", and not
+recommended for Anki 2.1.64+.
diff --git a/nixos/modules/services/misc/anki-sync-server.nix b/nixos/modules/services/misc/anki-sync-server.nix
new file mode 100644
index 0000000000000..a653820094176
--- /dev/null
+++ b/nixos/modules/services/misc/anki-sync-server.nix
@@ -0,0 +1,140 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.services.anki-sync-server;
+  name = "anki-sync-server";
+  specEscape = replaceStrings ["%"] ["%%"];
+  usersWithIndexes =
+    lists.imap1 (i: user: {
+      i = i;
+      user = user;
+    })
+    cfg.users;
+  usersWithIndexesFile = filter (x: x.user.passwordFile != null) usersWithIndexes;
+  usersWithIndexesNoFile = filter (x: x.user.passwordFile == null && x.user.password != null) usersWithIndexes;
+  anki-sync-server-run = pkgs.writeShellScriptBin "anki-sync-server-run" ''
+    # When services.anki-sync-server.users.passwordFile is set,
+    # each password file is passed as a systemd credential, which is mounted in
+    # a file system exposed to the service. Here we read the passwords from
+    # the credential files to pass them as environment variables to the Anki
+    # sync server.
+    ${
+      concatMapStringsSep
+      "\n"
+      (x: ''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:"''$(cat "''${CREDENTIALS_DIRECTORY}/"${escapeShellArg x.user.username})"'')
+      usersWithIndexesFile
+    }
+    # For users where services.anki-sync-server.users.password isn't set,
+    # export passwords in environment variables in plaintext.
+    ${
+      concatMapStringsSep
+      "\n"
+      (x: ''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:${escapeShellArg x.user.password}'')
+      usersWithIndexesNoFile
+    }
+    exec ${cfg.package}/bin/anki-sync-server
+  '';
+in {
+  options.services.anki-sync-server = {
+    enable = mkEnableOption "anki-sync-server";
+
+    package = mkPackageOption pkgs "anki-sync-server" { };
+
+    address = mkOption {
+      type = types.str;
+      default = "::1";
+      description = ''
+        IP address anki-sync-server listens to.
+        Note host names are not resolved.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 27701;
+      description = "Port number anki-sync-server listens to.";
+    };
+
+    openFirewall = mkOption {
+      default = false;
+      type = types.bool;
+      description = "Whether to open the firewall for the specified port.";
+    };
+
+    users = mkOption {
+      type = with types;
+        listOf (submodule {
+          options = {
+            username = mkOption {
+              type = str;
+              description = "User name accepted by anki-sync-server.";
+            };
+            password = mkOption {
+              type = nullOr str;
+              default = null;
+              description = ''
+                Password accepted by anki-sync-server for the associated username.
+                **WARNING**: This option is **not secure**. This password will
+                be stored in *plaintext* and will be visible to *all users*.
+                See {option}`services.anki-sync-server.users.passwordFile` for
+                a more secure option.
+              '';
+            };
+            passwordFile = mkOption {
+              type = nullOr path;
+              default = null;
+              description = ''
+                File containing the password accepted by anki-sync-server for
+                the associated username.  Make sure to make readable only by
+                root.
+              '';
+            };
+          };
+        });
+      description = "List of user-password pairs to provide to the sync server.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (builtins.length usersWithIndexesFile) + (builtins.length usersWithIndexesNoFile) > 0;
+        message = "At least one username-password pair must be set.";
+      }
+    ];
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.port];
+
+    systemd.services.anki-sync-server = {
+      description = "anki-sync-server: Anki sync server built into Anki";
+      after = ["network.target"];
+      wantedBy = ["multi-user.target"];
+      path = [cfg.package];
+      environment = {
+        SYNC_BASE = "%S/%N";
+        SYNC_HOST = specEscape cfg.address;
+        SYNC_PORT = toString cfg.port;
+      };
+
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        StateDirectory = name;
+        ExecStart = "${anki-sync-server-run}/bin/anki-sync-server-run";
+        Restart = "always";
+        LoadCredential =
+          map
+          (x: "${specEscape x.user.username}:${specEscape (toString x.user.passwordFile)}")
+          usersWithIndexesFile;
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with maintainers; [telotortium];
+    doc = ./anki-sync-server.md;
+  };
+}
diff --git a/nixos/modules/services/misc/ankisyncd.nix b/nixos/modules/services/misc/ankisyncd.nix
index 7be8dc7dab8ff..f5acfbb0ee969 100644
--- a/nixos/modules/services/misc/ankisyncd.nix
+++ b/nixos/modules/services/misc/ankisyncd.nix
@@ -24,12 +24,7 @@ in
     options.services.ankisyncd = {
       enable = mkEnableOption (lib.mdDoc "ankisyncd");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.ankisyncd;
-        defaultText = literalExpression "pkgs.ankisyncd";
-        description = lib.mdDoc "The package to use for the ankisyncd command.";
-      };
+      package = mkPackageOption pkgs "ankisyncd" { };
 
       host = mkOption {
         type = types.str;
@@ -51,6 +46,12 @@ in
     };
 
     config = mkIf cfg.enable {
+      warnings = [
+        ''
+        `services.ankisyncd` has been replaced by `services.anki-sync-server` and will be removed after
+        24.05 because anki-sync-server(-rs and python) are not maintained.
+        ''
+      ];
       networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
 
       systemd.services.ankisyncd = {
diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix
index 598907aaf1c61..b7281a0d9d5f5 100644
--- a/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixos/modules/services/misc/apache-kafka.nix
@@ -5,75 +5,117 @@ with lib;
 let
   cfg = config.services.apache-kafka;
 
-  serverProperties =
-    if cfg.serverProperties != null then
-      cfg.serverProperties
-    else
-      ''
-        # Generated by nixos
-        broker.id=${toString cfg.brokerId}
-        port=${toString cfg.port}
-        host.name=${cfg.hostname}
-        log.dirs=${concatStringsSep "," cfg.logDirs}
-        zookeeper.connect=${cfg.zookeeper}
-        ${toString cfg.extraProperties}
-      '';
+  # The `javaProperties` generator takes care of various escaping rules and
+  # generation of the properties file, but we'll handle stringly conversion
+  # ourselves in mkPropertySettings and stringlySettings, since we know more
+  # about the specifically allowed format eg. for lists of this type, and we
+  # don't want to coerce-downsample values to str too early by having the
+  # coercedTypes from javaProperties directly in our NixOS option types.
+  #
+  # Make sure every `freeformType` and any specific option type in `settings` is
+  # supported here.
+
+  mkPropertyString = let
+    render = {
+      bool = boolToString;
+      int = toString;
+      list = concatMapStringsSep "," mkPropertyString;
+      string = id;
+    };
+  in
+    v: render.${builtins.typeOf v} v;
 
-  serverConfig = pkgs.writeText "server.properties" serverProperties;
-  logConfig = pkgs.writeText "log4j.properties" cfg.log4jProperties;
+  stringlySettings = mapAttrs (_: mkPropertyString)
+    (filterAttrs (_: v:  v != null) cfg.settings);
 
+  generator = (pkgs.formats.javaProperties {}).generate;
 in {
 
   options.services.apache-kafka = {
-    enable = mkOption {
-      description = lib.mdDoc "Whether to enable Apache Kafka.";
-      default = false;
-      type = types.bool;
-    };
-
-    brokerId = mkOption {
-      description = lib.mdDoc "Broker ID.";
-      default = -1;
-      type = types.int;
-    };
+    enable = mkEnableOption (lib.mdDoc "Apache Kafka event streaming broker");
 
-    port = mkOption {
-      description = lib.mdDoc "Port number the broker should listen on.";
-      default = 9092;
-      type = types.port;
+    settings = mkOption {
+      description = lib.mdDoc ''
+        [Kafka broker configuration](https://kafka.apache.org/documentation.html#brokerconfigs)
+        {file}`server.properties`.
+
+        Note that .properties files contain mappings from string to string.
+        Keys with dots are NOT represented by nested attrs in these settings,
+        but instead as quoted strings (ie. `settings."broker.id"`, NOT
+        `settings.broker.id`).
+     '';
+      type = types.submodule {
+        freeformType = with types; let
+          primitive = oneOf [bool int str];
+        in lazyAttrsOf (nullOr (either primitive (listOf primitive)));
+
+        options = {
+          "broker.id" = mkOption {
+            description = lib.mdDoc "Broker ID. -1 or null to auto-allocate in zookeeper mode.";
+            default = null;
+            type = with types; nullOr int;
+          };
+
+          "log.dirs" = mkOption {
+            description = lib.mdDoc "Log file directories.";
+            # Deliberaly leave out old default and use the rewrite opportunity
+            # to have users choose a safer value -- /tmp might be volatile and is a
+            # slightly scary default choice.
+            # default = [ "/tmp/apache-kafka" ];
+            type = with types; listOf path;
+          };
+
+          "listeners" = mkOption {
+            description = lib.mdDoc ''
+              Kafka Listener List.
+              See [listeners](https://kafka.apache.org/documentation/#brokerconfigs_listeners).
+            '';
+            type = types.listOf types.str;
+            default = [ "PLAINTEXT://localhost:9092" ];
+          };
+        };
+      };
     };
 
-    hostname = mkOption {
-      description = lib.mdDoc "Hostname the broker should bind to.";
-      default = "localhost";
-      type = types.str;
+    clusterId = mkOption {
+      description = lib.mdDoc ''
+        KRaft mode ClusterId used for formatting log directories. Can be generated with `kafka-storage.sh random-uuid`
+      '';
+      type = with types; nullOr str;
+      default = null;
     };
 
-    logDirs = mkOption {
-      description = lib.mdDoc "Log file directories";
-      default = [ "/tmp/kafka-logs" ];
-      type = types.listOf types.path;
+    configFiles.serverProperties = mkOption {
+      description = lib.mdDoc ''
+        Kafka server.properties configuration file path.
+        Defaults to the rendered `settings`.
+      '';
+      type = types.path;
     };
 
-    zookeeper = mkOption {
-      description = lib.mdDoc "Zookeeper connection string";
-      default = "localhost:2181";
-      type = types.str;
+    configFiles.log4jProperties = mkOption {
+      description = lib.mdDoc "Kafka log4j property configuration file path";
+      type = types.path;
+      default = pkgs.writeText "log4j.properties" cfg.log4jProperties;
+      defaultText = ''pkgs.writeText "log4j.properties" cfg.log4jProperties'';
     };
 
-    extraProperties = mkOption {
-      description = lib.mdDoc "Extra properties for server.properties.";
-      type = types.nullOr types.lines;
-      default = null;
+    formatLogDirs = mkOption {
+      description = lib.mdDoc ''
+        Whether to format log dirs in KRaft mode if all log dirs are
+        unformatted, ie. they contain no meta.properties.
+      '';
+      type = types.bool;
+      default = false;
     };
 
-    serverProperties = mkOption {
+    formatLogDirsIgnoreFormatted = mkOption {
       description = lib.mdDoc ''
-        Complete server.properties content. Other server.properties config
-        options will be ignored if this option is used.
+        Whether to ignore already formatted log dirs when formatting log dirs,
+        instead of failing. Useful when replacing or adding disks.
       '';
-      type = types.nullOr types.lines;
-      default = null;
+      type = types.bool;
+      default = false;
     };
 
     log4jProperties = mkOption {
@@ -99,12 +141,7 @@ in {
       ];
     };
 
-    package = mkOption {
-      description = lib.mdDoc "The kafka package to use";
-      default = pkgs.apacheKafka;
-      defaultText = literalExpression "pkgs.apacheKafka";
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "apacheKafka" { };
 
     jre = mkOption {
       description = lib.mdDoc "The JRE with which to run Kafka";
@@ -112,40 +149,70 @@ in {
       defaultText = literalExpression "pkgs.apacheKafka.passthru.jre";
       type = types.package;
     };
-
   };
 
-  config = mkIf cfg.enable {
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "apache-kafka" "brokerId" ]
+      [ "services" "apache-kafka" "settings" ''broker.id'' ])
+    (mkRenamedOptionModule
+      [ "services" "apache-kafka" "logDirs" ]
+      [ "services" "apache-kafka" "settings" ''log.dirs'' ])
+    (mkRenamedOptionModule
+      [ "services" "apache-kafka" "zookeeper" ]
+      [ "services" "apache-kafka" "settings" ''zookeeper.connect'' ])
+
+    (mkRemovedOptionModule [ "services" "apache-kafka" "port" ]
+      "Please see services.apache-kafka.settings.listeners and its documentation instead")
+    (mkRemovedOptionModule [ "services" "apache-kafka" "hostname" ]
+      "Please see services.apache-kafka.settings.listeners and its documentation instead")
+    (mkRemovedOptionModule [ "services" "apache-kafka" "extraProperties" ]
+      "Please see services.apache-kafka.settings and its documentation instead")
+    (mkRemovedOptionModule [ "services" "apache-kafka" "serverProperties" ]
+      "Please see services.apache-kafka.settings and its documentation instead")
+  ];
 
-    environment.systemPackages = [cfg.package];
+  config = mkIf cfg.enable {
+    services.apache-kafka.configFiles.serverProperties = generator "server.properties" stringlySettings;
 
     users.users.apache-kafka = {
       isSystemUser = true;
       group = "apache-kafka";
       description = "Apache Kafka daemon user";
-      home = head cfg.logDirs;
     };
     users.groups.apache-kafka = {};
 
-    systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") cfg.logDirs;
+    systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") cfg.settings."log.dirs";
 
     systemd.services.apache-kafka = {
       description = "Apache Kafka Daemon";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
+      preStart = mkIf cfg.formatLogDirs
+        (if cfg.formatLogDirsIgnoreFormatted then ''
+          ${cfg.package}/bin/kafka-storage.sh format -t "${cfg.clusterId}" -c ${cfg.configFiles.serverProperties} --ignore-formatted
+        '' else ''
+          if ${concatMapStringsSep " && " (l: ''[ ! -f "${l}/meta.properties" ]'') cfg.settings."log.dirs"}; then
+            ${cfg.package}/bin/kafka-storage.sh format -t "${cfg.clusterId}" -c ${cfg.configFiles.serverProperties}
+          fi
+        '');
       serviceConfig = {
         ExecStart = ''
           ${cfg.jre}/bin/java \
             -cp "${cfg.package}/libs/*" \
-            -Dlog4j.configuration=file:${logConfig} \
+            -Dlog4j.configuration=file:${cfg.configFiles.log4jProperties} \
             ${toString cfg.jvmOptions} \
             kafka.Kafka \
-            ${serverConfig}
+            ${cfg.configFiles.serverProperties}
         '';
         User = "apache-kafka";
         SuccessExitStatus = "0 143";
       };
     };
-
   };
+
+  meta.doc = ./kafka.md;
+  meta.maintainers = with lib.maintainers; [
+    srhb
+  ];
 }
diff --git a/nixos/modules/services/misc/atuin.nix b/nixos/modules/services/misc/atuin.nix
index 57ff02df7d68b..2d6ffc510ce55 100644
--- a/nixos/modules/services/misc/atuin.nix
+++ b/nixos/modules/services/misc/atuin.nix
@@ -6,7 +6,7 @@ in
 {
   options = {
     services.atuin = {
-      enable = lib.mkEnableOption (mdDoc "Enable server for shell history sync with atuin");
+      enable = lib.mkEnableOption (mdDoc "Atuin server for shell history sync");
 
       openRegistration = mkOption {
         type = types.bool;
@@ -73,9 +73,7 @@ in
       enable = true;
       ensureUsers = [{
         name = "atuin";
-        ensurePermissions = {
-          "DATABASE atuin" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       }];
       ensureDatabases = [ "atuin" ];
     };
diff --git a/nixos/modules/services/misc/autofs.nix b/nixos/modules/services/misc/autofs.nix
index 55ab15ff003d1..723b67e8bb6bc 100644
--- a/nixos/modules/services/misc/autofs.nix
+++ b/nixos/modules/services/misc/autofs.nix
@@ -74,7 +74,7 @@ in
 
   config = mkIf cfg.enable {
 
-    boot.kernelModules = [ "autofs4" ];
+    boot.kernelModules = [ "autofs" ];
 
     systemd.services.autofs =
       { description = "Automounts filesystems on demand";
diff --git a/nixos/modules/services/misc/autosuspend.nix b/nixos/modules/services/misc/autosuspend.nix
index b3e362533a093..28dfa12105ec7 100644
--- a/nixos/modules/services/misc/autosuspend.nix
+++ b/nixos/modules/services/misc/autosuspend.nix
@@ -1,7 +1,7 @@
 { config, pkgs, lib, ... }:
 let
   inherit (lib) mapAttrs' nameValuePair filterAttrs types mkEnableOption
-    mdDoc mkPackageOptionMD mkOption literalExpression mkIf flatten
+    mdDoc mkPackageOption mkOption literalExpression mkIf flatten
     maintainers attrValues;
 
   cfg = config.services.autosuspend;
@@ -96,7 +96,7 @@ in
     services.autosuspend = {
       enable = mkEnableOption (mdDoc "the autosuspend daemon");
 
-      package = mkPackageOptionMD pkgs "autosuspend" { };
+      package = mkPackageOption pkgs "autosuspend" { };
 
       settings = mkOption {
         type = types.submodule {
diff --git a/nixos/modules/services/misc/bcg.nix b/nixos/modules/services/misc/bcg.nix
index 214c89dbfe72e..ad0b9c871342f 100644
--- a/nixos/modules/services/misc/bcg.nix
+++ b/nixos/modules/services/misc/bcg.nix
@@ -26,12 +26,7 @@ in
   options = {
     services.bcg = {
       enable = mkEnableOption (mdDoc "BigClown gateway");
-      package = mkOption {
-        default = pkgs.python3Packages.bcg;
-        defaultText = literalExpression "pkgs.python3Packages.bcg";
-        description = mdDoc "Which bcg derivation to use.";
-        type = types.package;
-      };
+      package = mkPackageOption pkgs [ "python3Packages" "bcg" ] { };
       environmentFiles = mkOption {
         type = types.listOf types.path;
         default = [];
@@ -159,7 +154,7 @@ in
     in {
       description = "BigClown Gateway";
       wantedBy = [ "multi-user.target" ];
-      wants = mkIf config.services.mosquitto.enable [ "mosquitto.service" ];
+      wants = [ "network-online.target" ] ++ lib.optional config.services.mosquitto.enable "mosquitto.service";
       after = [ "network-online.target" ];
       preStart = ''
         umask 077
diff --git a/nixos/modules/services/misc/calibre-server.nix b/nixos/modules/services/misc/calibre-server.nix
index e1ddae1de1f8d..66ae5fa91bb68 100644
--- a/nixos/modules/services/misc/calibre-server.nix
+++ b/nixos/modules/services/misc/calibre-server.nix
@@ -33,7 +33,7 @@ in
     services.calibre-server = {
 
       enable = mkEnableOption (lib.mdDoc "calibre-server");
-      package = lib.mkPackageOptionMD pkgs "calibre" { };
+      package = lib.mkPackageOption pkgs "calibre" { };
 
       libraries = mkOption {
         type = types.listOf types.path;
diff --git a/nixos/modules/services/misc/cfdyndns.nix b/nixos/modules/services/misc/cfdyndns.nix
index 9cd8b188ffaec..dba8ac2001514 100644
--- a/nixos/modules/services/misc/cfdyndns.nix
+++ b/nixos/modules/services/misc/cfdyndns.nix
@@ -23,6 +23,15 @@ in
         '';
       };
 
+      apiTokenFile = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = lib.mdDoc ''
+          The path to a file containing the API Token
+          used to authenticate with CloudFlare.
+        '';
+      };
+
       apikeyFile = mkOption {
         default = null;
         type = types.nullOr types.str;
@@ -51,32 +60,22 @@ in
       startAt = "*:0/5";
       serviceConfig = {
         Type = "simple";
-        User = config.ids.uids.cfdyndns;
-        Group = config.ids.gids.cfdyndns;
+        LoadCredential = lib.optional (cfg.apiTokenFile != null) "CLOUDFLARE_APITOKEN_FILE:${cfg.apiTokenFile}";
+        DynamicUser = true;
       };
       environment = {
-        CLOUDFLARE_EMAIL="${cfg.email}";
         CLOUDFLARE_RECORDS="${concatStringsSep "," cfg.records}";
       };
       script = ''
         ${optionalString (cfg.apikeyFile != null) ''
           export CLOUDFLARE_APIKEY="$(cat ${escapeShellArg cfg.apikeyFile})"
+          export CLOUDFLARE_EMAIL="${cfg.email}"
+        ''}
+        ${optionalString (cfg.apiTokenFile != null) ''
+          export CLOUDFLARE_APITOKEN=$(${pkgs.systemd}/bin/systemd-creds cat CLOUDFLARE_APITOKEN_FILE)
         ''}
         ${pkgs.cfdyndns}/bin/cfdyndns
       '';
     };
-
-    users.users = {
-      cfdyndns = {
-        group = "cfdyndns";
-        uid = config.ids.uids.cfdyndns;
-      };
-    };
-
-    users.groups = {
-      cfdyndns = {
-        gid = config.ids.gids.cfdyndns;
-      };
-    };
   };
 }
diff --git a/nixos/modules/services/misc/cgminer.nix b/nixos/modules/services/misc/cgminer.nix
index a6fbfee73bad3..ad6cbf50918d5 100644
--- a/nixos/modules/services/misc/cgminer.nix
+++ b/nixos/modules/services/misc/cgminer.nix
@@ -33,12 +33,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "cgminer, an ASIC/FPGA/GPU miner for bitcoin and litecoin");
 
-      package = mkOption {
-        default = pkgs.cgminer;
-        defaultText = literalExpression "pkgs.cgminer";
-        description = lib.mdDoc "Which cgminer derivation to use.";
-        type = types.package;
-      };
+      package = mkPackageOption pkgs "cgminer" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/misc/clipcat.nix b/nixos/modules/services/misc/clipcat.nix
index 0129de3a9efb0..fb64427095301 100644
--- a/nixos/modules/services/misc/clipcat.nix
+++ b/nixos/modules/services/misc/clipcat.nix
@@ -9,12 +9,7 @@ in {
   options.services.clipcat= {
     enable = mkEnableOption (lib.mdDoc "Clipcat clipboard daemon");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.clipcat;
-      defaultText = literalExpression "pkgs.clipcat";
-      description = lib.mdDoc "clipcat derivation to use.";
-    };
+    package = mkPackageOption pkgs "clipcat" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/misc/clipmenu.nix b/nixos/modules/services/misc/clipmenu.nix
index 1cc8c4c47f7ec..343167b1df2e2 100644
--- a/nixos/modules/services/misc/clipmenu.nix
+++ b/nixos/modules/services/misc/clipmenu.nix
@@ -9,12 +9,7 @@ in {
   options.services.clipmenu = {
     enable = mkEnableOption (lib.mdDoc "clipmenu, the clipboard management daemon");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.clipmenu;
-      defaultText = literalExpression "pkgs.clipmenu";
-      description = lib.mdDoc "clipmenu derivation to use.";
-    };
+    package = mkPackageOption pkgs "clipmenu" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/misc/confd.nix b/nixos/modules/services/misc/confd.nix
index 17c1be57ccbcd..93731547ede8b 100755..100644
--- a/nixos/modules/services/misc/confd.nix
+++ b/nixos/modules/services/misc/confd.nix
@@ -61,12 +61,7 @@ in {
       type = types.path;
     };
 
-    package = mkOption {
-      description = lib.mdDoc "Confd package to use.";
-      default = pkgs.confd;
-      defaultText = literalExpression "pkgs.confd";
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "confd" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix
index 13c57ce6b85bf..ee342cbc2e474 100644
--- a/nixos/modules/services/misc/disnix.nix
+++ b/nixos/modules/services/misc/disnix.nix
@@ -27,12 +27,7 @@ in
 
       useWebServiceInterface = mkEnableOption (lib.mdDoc "the DisnixWebService interface running on Apache Tomcat");
 
-      package = mkOption {
-        type = types.path;
-        description = lib.mdDoc "The Disnix package";
-        default = pkgs.disnix;
-        defaultText = literalExpression "pkgs.disnix";
-      };
+      package = mkPackageOption pkgs "disnix" {};
 
       enableProfilePath = mkEnableOption (lib.mdDoc "exposing the Disnix profiles in the system's PATH");
 
diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix
index b0e910634637c..e8fbc05423d31 100644
--- a/nixos/modules/services/misc/docker-registry.nix
+++ b/nixos/modules/services/misc/docker-registry.nix
@@ -47,12 +47,8 @@ in {
   options.services.dockerRegistry = {
     enable = mkEnableOption (lib.mdDoc "Docker Registry");
 
-    package = mkOption {
-      type = types.package;
-      description = mdDoc "Which Docker registry package to use.";
-      default = pkgs.docker-distribution;
-      defaultText = literalExpression "pkgs.docker-distribution";
-      example = literalExpression "pkgs.gitlab-container-registry";
+    package = mkPackageOption pkgs "docker-distribution" {
+      example = "gitlab-container-registry";
     };
 
     listenAddress = mkOption {
diff --git a/nixos/modules/services/misc/domoticz.nix b/nixos/modules/services/misc/domoticz.nix
index fd9fcf0b78eb5..315092f933514 100644
--- a/nixos/modules/services/misc/domoticz.nix
+++ b/nixos/modules/services/misc/domoticz.nix
@@ -35,6 +35,7 @@ in {
     systemd.services."domoticz" = {
       description = pkgDesc;
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       serviceConfig = {
         DynamicUser = true;
diff --git a/nixos/modules/services/misc/dwm-status.nix b/nixos/modules/services/misc/dwm-status.nix
index de3e28c41d27d..351adf31d922a 100644
--- a/nixos/modules/services/misc/dwm-status.nix
+++ b/nixos/modules/services/misc/dwm-status.nix
@@ -24,14 +24,8 @@ in
 
       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 = lib.mdDoc ''
-          Which dwm-status package to use.
-        '';
+      package = mkPackageOption pkgs "dwm-status" {
+        example = "dwm-status.override { enableAlsaUtils = false; }";
       };
 
       order = mkOption {
diff --git a/nixos/modules/services/misc/dysnomia.nix b/nixos/modules/services/misc/dysnomia.nix
index 0f92265ccbea6..129345e38106b 100644
--- a/nixos/modules/services/misc/dysnomia.nix
+++ b/nixos/modules/services/misc/dysnomia.nix
@@ -223,7 +223,7 @@ in
       ejabberdUser = config.services.ejabberd.user;
     }; }
     // lib.optionalAttrs (config.services.mysql.enable) { mysql-database = {
-        mysqlPort = config.services.mysql.port;
+        mysqlPort = config.services.mysql.settings.mysqld.port;
         mysqlSocket = "/run/mysqld/mysqld.sock";
       } // lib.optionalAttrs cfg.enableAuthentication {
         mysqlUsername = "root";
diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix
index 7bc7a94991131..73bdeb3b0afdf 100644
--- a/nixos/modules/services/misc/etcd.nix
+++ b/nixos/modules/services/misc/etcd.nix
@@ -15,7 +15,7 @@ in {
       type = types.bool;
     };
 
-    package = mkPackageOptionMD pkgs "etcd" { };
+    package = mkPackageOption pkgs "etcd" { };
 
     name = mkOption {
       description = lib.mdDoc "Etcd unique node name.";
diff --git a/nixos/modules/services/misc/etesync-dav.nix b/nixos/modules/services/misc/etesync-dav.nix
index 9d99d548d95b0..ae2b5ad043433 100644
--- a/nixos/modules/services/misc/etesync-dav.nix
+++ b/nixos/modules/services/misc/etesync-dav.nix
@@ -59,6 +59,7 @@ in
 
       systemd.services.etesync-dav = {
         description = "etesync-dav - A CalDAV and CardDAV adapter for EteSync";
+        wants = [ "network-online.target" ];
         after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         path = [ pkgs.etesync-dav ];
diff --git a/nixos/modules/services/misc/forgejo.md b/nixos/modules/services/misc/forgejo.md
new file mode 100644
index 0000000000000..14b21933e6b09
--- /dev/null
+++ b/nixos/modules/services/misc/forgejo.md
@@ -0,0 +1,79 @@
+# Forgejo {#module-forgejo}
+
+Forgejo is a soft-fork of gitea, with strong community focus, as well
+as on self-hosting and federation. [Codeberg](https://codeberg.org) is
+deployed from it.
+
+See [upstream docs](https://forgejo.org/docs/latest/).
+
+The method of choice for running forgejo is using [`services.forgejo`](#opt-services.forgejo.enable).
+
+::: {.warning}
+Running forgejo using `services.gitea.package = pkgs.forgejo` is no longer
+recommended.
+If you experience issues with your instance using `services.gitea`,
+**DO NOT** report them to the `services.gitea` module maintainers.
+**DO** report them to the `services.forgejo` module maintainers instead.
+:::
+
+## Migration from Gitea {#module-forgejo-migration-gitea}
+
+::: {.note}
+Migrating is, while not strictly necessary at this point, highly recommended.
+Both modules and projects are likely to diverge further with each release.
+Which might lead to an even more involved migration.
+:::
+
+### Full-Migration {#module-forgejo-migration-gitea-default}
+
+This will migrate the state directory (data), rename and chown the database and
+delete the gitea user.
+
+::: {.note}
+This will also change the git remote ssh-url user from `gitea@` to `forgejo@`,
+when using the host's openssh server (default) instead of the integrated one.
+:::
+
+Instructions for PostgreSQL (default). Adapt accordingly for other databases:
+
+```sh
+systemctl stop gitea
+mv /var/lib/gitea /var/lib/forgejo
+runuser -u postgres -- psql -c '
+  ALTER USER gitea RENAME TO forgejo;
+  ALTER DATABASE gitea RENAME TO forgejo;
+'
+nixos-rebuild switch
+systemctl stop forgejo
+chown -R forgejo:forgejo /var/lib/forgejo
+systemctl restart forgejo
+```
+
+### Alternatively, keeping the gitea user {#module-forgejo-migration-gitea-impersonate}
+
+Alternatively, instead of renaming the database, copying the state folder and
+changing the user, the forgejo module can be set up to re-use the old storage
+locations and database, instead of having to copy or rename them.
+Make sure to disable `services.gitea`, when doing this.
+
+```nix
+services.gitea.enable = false;
+
+services.forgejo = {
+  enable = true;
+  user = "gitea";
+  group = "gitea";
+  stateDir = "/var/lib/gitea";
+  database.name = "gitea";
+  database.user = "gitea";
+};
+
+users.users.gitea = {
+  home = "/var/lib/gitea";
+  useDefaultShell = true;
+  group = "gitea";
+  isSystemUser = true;
+};
+
+users.groups.gitea = {};
+```
diff --git a/nixos/modules/services/misc/forgejo.nix b/nixos/modules/services/misc/forgejo.nix
new file mode 100644
index 0000000000000..08cddc3a07105
--- /dev/null
+++ b/nixos/modules/services/misc/forgejo.nix
@@ -0,0 +1,679 @@
+{ config, lib, options, pkgs, ... }:
+
+let
+  cfg = config.services.forgejo;
+  opt = options.services.forgejo;
+  format = pkgs.formats.ini { };
+
+  exe = lib.getExe cfg.package;
+
+  pg = config.services.postgresql;
+  useMysql = cfg.database.type == "mysql";
+  usePostgresql = cfg.database.type == "postgres";
+  useSqlite = cfg.database.type == "sqlite3";
+
+  inherit (lib)
+    literalExpression
+    mdDoc
+    mkChangedOptionModule
+    mkDefault
+    mkEnableOption
+    mkIf
+    mkMerge
+    mkOption
+    mkPackageOption
+    mkRemovedOptionModule
+    mkRenamedOptionModule
+    optionalAttrs
+    optionals
+    optionalString
+    types
+    ;
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "forgejo" "appName" ] [ "services" "forgejo" "settings" "DEFAULT" "APP_NAME" ])
+    (mkRemovedOptionModule [ "services" "forgejo" "extraConfig" ] "services.forgejo.extraConfig has been removed. Please use the freeform services.forgejo.settings option instead")
+    (mkRemovedOptionModule [ "services" "forgejo" "database" "password" ] "services.forgejo.database.password has been removed. Please use services.forgejo.database.passwordFile instead")
+
+    # copied from services.gitea; remove at some point
+    (mkRenamedOptionModule [ "services" "forgejo" "cookieSecure" ] [ "services" "forgejo" "settings" "session" "COOKIE_SECURE" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "disableRegistration" ] [ "services" "forgejo" "settings" "service" "DISABLE_REGISTRATION" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "domain" ] [ "services" "forgejo" "settings" "server" "DOMAIN" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "httpAddress" ] [ "services" "forgejo" "settings" "server" "HTTP_ADDR" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "httpPort" ] [ "services" "forgejo" "settings" "server" "HTTP_PORT" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "log" "level" ] [ "services" "forgejo" "settings" "log" "LEVEL" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "log" "rootPath" ] [ "services" "forgejo" "settings" "log" "ROOT_PATH" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "rootUrl" ] [ "services" "forgejo" "settings" "server" "ROOT_URL" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "ssh" "clonePort" ] [ "services" "forgejo" "settings" "server" "SSH_PORT" ])
+    (mkRenamedOptionModule [ "services" "forgejo" "staticRootPath" ] [ "services" "forgejo" "settings" "server" "STATIC_ROOT_PATH" ])
+    (mkChangedOptionModule [ "services" "forgejo" "enableUnixSocket" ] [ "services" "forgejo" "settings" "server" "PROTOCOL" ] (
+      config: if config.services.forgejo.enableUnixSocket then "http+unix" else "http"
+    ))
+    (mkRemovedOptionModule [ "services" "forgejo" "ssh" "enable" ] "services.forgejo.ssh.enable has been migrated into freeform setting services.forgejo.settings.server.DISABLE_SSH. Keep in mind that the setting is inverted")
+  ];
+
+  options = {
+    services.forgejo = {
+      enable = mkEnableOption (mdDoc "Forgejo");
+
+      package = mkPackageOption pkgs "forgejo" { };
+
+      useWizard = mkOption {
+        default = false;
+        type = types.bool;
+        description = mdDoc ''
+          Whether to use the built-in installation wizard instead of
+          declaratively managing the {file}`app.ini` config file in nix.
+        '';
+      };
+
+      stateDir = mkOption {
+        default = "/var/lib/forgejo";
+        type = types.str;
+        description = mdDoc "Forgejo data directory.";
+      };
+
+      customDir = mkOption {
+        default = "${cfg.stateDir}/custom";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/custom"'';
+        type = types.str;
+        description = mdDoc ''
+          Base directory for custom templates and other options.
+
+          If {option}`${opt.useWizard}` is disabled (default), this directory will also
+          hold secrets and the resulting {file}`app.ini` config at runtime.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "forgejo";
+        description = mdDoc "User account under which Forgejo runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "forgejo";
+        description = mdDoc "Group under which Forgejo runs.";
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "sqlite3" "mysql" "postgres" ];
+          example = "mysql";
+          default = "sqlite3";
+          description = mdDoc "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = mdDoc "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = if !usePostgresql then 3306 else pg.port;
+          defaultText = literalExpression ''
+            if config.${opt.database.type} != "postgresql"
+            then 3306
+            else config.${options.services.postgresql.port}
+          '';
+          description = mdDoc "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "forgejo";
+          description = mdDoc "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "forgejo";
+          description = mdDoc "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/forgejo-dbpassword";
+          description = mdDoc ''
+            A file containing the password corresponding to
+            {option}`${opt.database.user}`.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
+          defaultText = literalExpression "null";
+          example = "/run/mysqld/mysqld.sock";
+          description = mdDoc "Path to the unix socket file to use for authentication.";
+        };
+
+        path = mkOption {
+          type = types.str;
+          default = "${cfg.stateDir}/data/forgejo.db";
+          defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/forgejo.db"'';
+          description = mdDoc "Path to the sqlite3 database file.";
+        };
+
+        createDatabase = mkOption {
+          type = types.bool;
+          default = true;
+          description = mdDoc "Whether to create a local database automatically.";
+        };
+      };
+
+      dump = {
+        enable = mkEnableOption (mdDoc "periodic dumps via the [built-in {command}`dump` command](https://forgejo.org/docs/latest/admin/command-line/#dump)");
+
+        interval = mkOption {
+          type = types.str;
+          default = "04:31";
+          example = "hourly";
+          description = mdDoc ''
+            Run a Forgejo dump at this interval. Runs by default at 04:31 every day.
+
+            The format is described in
+            {manpage}`systemd.time(7)`.
+          '';
+        };
+
+        backupDir = mkOption {
+          type = types.str;
+          default = "${cfg.stateDir}/dump";
+          defaultText = literalExpression ''"''${config.${opt.stateDir}}/dump"'';
+          description = mdDoc "Path to the directory where the dump archives will be stored.";
+        };
+
+        type = mkOption {
+          type = types.enum [ "zip" "tar" "tar.sz" "tar.gz" "tar.xz" "tar.bz2" "tar.br" "tar.lz4" "tar.zst" ];
+          default = "zip";
+          description = mdDoc "Archive format used to store the dump file.";
+        };
+
+        file = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = mdDoc "Filename to be used for the dump. If `null` a default name is chosen by forgejo.";
+          example = "forgejo-dump";
+        };
+      };
+
+      lfs = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = mdDoc "Enables git-lfs support.";
+        };
+
+        contentDir = mkOption {
+          type = types.str;
+          default = "${cfg.stateDir}/data/lfs";
+          defaultText = literalExpression ''"''${config.${opt.stateDir}}/data/lfs"'';
+          description = mdDoc "Where to store LFS files.";
+        };
+      };
+
+      repositoryRoot = mkOption {
+        type = types.str;
+        default = "${cfg.stateDir}/repositories";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/repositories"'';
+        description = mdDoc "Path to the git repositories.";
+      };
+
+      mailerPasswordFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/run/keys/forgejo-mailpw";
+        description = mdDoc "Path to a file containing the SMTP password.";
+      };
+
+      settings = mkOption {
+        default = { };
+        description = mdDoc ''
+          Free-form settings written directly to the `app.ini` configfile file.
+          Refer to <https://forgejo.org/docs/latest/admin/config-cheat-sheet/> for supported values.
+        '';
+        example = literalExpression ''
+          {
+            DEFAULT = {
+              RUN_MODE = "dev";
+            };
+            "cron.sync_external_users" = {
+              RUN_AT_START = true;
+              SCHEDULE = "@every 24h";
+              UPDATE_EXISTING = true;
+            };
+            mailer = {
+              ENABLED = true;
+              MAILER_TYPE = "sendmail";
+              FROM = "do-not-reply@example.org";
+              SENDMAIL_PATH = "''${pkgs.system-sendmail}/bin/sendmail";
+            };
+            other = {
+              SHOW_FOOTER_VERSION = false;
+            };
+          }
+        '';
+        type = types.submodule {
+          freeformType = format.type;
+          options = {
+            log = {
+              ROOT_PATH = mkOption {
+                default = "${cfg.stateDir}/log";
+                defaultText = literalExpression ''"''${config.${opt.stateDir}}/log"'';
+                type = types.str;
+                description = mdDoc "Root path for log files.";
+              };
+              LEVEL = mkOption {
+                default = "Info";
+                type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
+                description = mdDoc "General log level.";
+              };
+            };
+
+            server = {
+              PROTOCOL = mkOption {
+                type = types.enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ];
+                default = "http";
+                description = mdDoc ''Listen protocol. `+unix` means "over unix", not "in addition to."'';
+              };
+
+              HTTP_ADDR = mkOption {
+                type = types.either types.str types.path;
+                default = if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/forgejo/forgejo.sock" else "0.0.0.0";
+                defaultText = literalExpression ''if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/forgejo/forgejo.sock" else "0.0.0.0"'';
+                description = mdDoc "Listen address. Must be a path when using a unix socket.";
+              };
+
+              HTTP_PORT = mkOption {
+                type = types.port;
+                default = 3000;
+                description = mdDoc "Listen port. Ignored when using a unix socket.";
+              };
+
+              DOMAIN = mkOption {
+                type = types.str;
+                default = "localhost";
+                description = mdDoc "Domain name of your server.";
+              };
+
+              ROOT_URL = mkOption {
+                type = types.str;
+                default = "http://${cfg.settings.server.DOMAIN}:${toString cfg.settings.server.HTTP_PORT}/";
+                defaultText = literalExpression ''"http://''${config.services.forgejo.settings.server.DOMAIN}:''${toString config.services.forgejo.settings.server.HTTP_PORT}/"'';
+                description = mdDoc "Full public URL of Forgejo server.";
+              };
+
+              STATIC_ROOT_PATH = mkOption {
+                type = types.either types.str types.path;
+                default = cfg.package.data;
+                defaultText = literalExpression "config.${opt.package}.data";
+                example = "/var/lib/forgejo/data";
+                description = mdDoc "Upper level of template and static files path.";
+              };
+
+              DISABLE_SSH = mkOption {
+                type = types.bool;
+                default = false;
+                description = mdDoc "Disable external SSH feature.";
+              };
+
+              SSH_PORT = mkOption {
+                type = types.port;
+                default = 22;
+                example = 2222;
+                description = 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.
+                '';
+              };
+            };
+
+            session = {
+              COOKIE_SECURE = mkOption {
+                type = types.bool;
+                default = false;
+                description = mdDoc ''
+                  Marks session cookies as "secure" as a hint for browsers to only send
+                  them via HTTPS. This option is recommend, if Forgejo is being served over HTTPS.
+                '';
+              };
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
+        message = "services.forgejo.database.user must match services.forgejo.user if the database is to be automatically provisioned";
+      }
+      { assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.forgejo.createDatabase` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
+    ];
+
+    services.forgejo.settings = {
+      DEFAULT = {
+        RUN_MODE = mkDefault "prod";
+        RUN_USER = mkDefault cfg.user;
+        WORK_PATH = mkDefault cfg.stateDir;
+      };
+
+      database = mkMerge [
+        {
+          DB_TYPE = cfg.database.type;
+        }
+        (mkIf (useMysql || usePostgresql) {
+          HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
+          NAME = cfg.database.name;
+          USER = cfg.database.user;
+          PASSWD = "#dbpass#";
+        })
+        (mkIf useSqlite {
+          PATH = cfg.database.path;
+        })
+        (mkIf usePostgresql {
+          SSL_MODE = "disable";
+        })
+      ];
+
+      repository = {
+        ROOT = cfg.repositoryRoot;
+      };
+
+      server = mkIf cfg.lfs.enable {
+        LFS_START_SERVER = true;
+        LFS_JWT_SECRET = "#lfsjwtsecret#";
+      };
+
+      session = {
+        COOKIE_NAME = mkDefault "session";
+      };
+
+      security = {
+        SECRET_KEY = "#secretkey#";
+        INTERNAL_TOKEN = "#internaltoken#";
+        INSTALL_LOCK = true;
+      };
+
+      mailer = mkIf (cfg.mailerPasswordFile != null) {
+        PASSWD = "#mailerpass#";
+      };
+
+      oauth2 = {
+        JWT_SECRET = "#oauth2jwtsecret#";
+      };
+
+      lfs = mkIf cfg.lfs.enable {
+        PATH = cfg.lfs.contentDir;
+      };
+    };
+
+    services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
+      enable = mkDefault true;
+
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        {
+          name = cfg.database.user;
+          ensureDBOwnership = true;
+        }
+      ];
+    };
+
+    services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
+      enable = mkDefault true;
+      package = mkDefault pkgs.mariadb;
+
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        {
+          name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
+
+      # If we have a folder or symlink with Forgejo locales, remove it
+      # And symlink the current Forgejo locales in place
+      "L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale"
+
+    ] ++ optionals cfg.lfs.enable [
+      "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.forgejo = {
+      description = "Forgejo (Beyond coding. We forge.)";
+      after = [
+        "network.target"
+      ] ++ optionals usePostgresql [
+        "postgresql.service"
+      ] ++ optionals useMysql [
+        "mysql.service"
+      ];
+      requires = optionals (cfg.database.createDatabase && usePostgresql) [
+        "postgresql.service"
+      ] ++ optionals (cfg.database.createDatabase && useMysql) [
+        "mysql.service"
+      ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ cfg.package pkgs.git pkgs.gnupg ];
+
+      # In older versions the secret naming for JWT was kind of confusing.
+      # The file jwt_secret hold the value for LFS_JWT_SECRET and JWT_SECRET
+      # wasn't persistent at all.
+      # To fix that, there is now the file oauth2_jwt_secret containing the
+      # values for JWT_SECRET and the file jwt_secret gets renamed to
+      # lfs_jwt_secret.
+      # We have to consider this to stay compatible with older installations.
+      preStart =
+        let
+          runConfig = "${cfg.customDir}/conf/app.ini";
+          secretKey = "${cfg.customDir}/conf/secret_key";
+          oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret";
+          oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET
+          lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
+          internalToken = "${cfg.customDir}/conf/internal_token";
+          replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
+        in
+        ''
+          # copy custom configuration and generate random secrets if needed
+          ${lib.optionalString (!cfg.useWizard) ''
+            function forgejo_setup {
+              cp -f '${format.generate "app.ini" cfg.settings}' '${runConfig}'
+
+              if [ ! -s '${secretKey}' ]; then
+                  ${exe} generate secret SECRET_KEY > '${secretKey}'
+              fi
+
+              # Migrate LFS_JWT_SECRET filename
+              if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then
+                  mv '${oldLfsJwtSecret}' '${lfsJwtSecret}'
+              fi
+
+              if [ ! -s '${oauth2JwtSecret}' ]; then
+                  ${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}'
+              fi
+
+              ${optionalString cfg.lfs.enable ''
+              if [ ! -s '${lfsJwtSecret}' ]; then
+                  ${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}'
+              fi
+              ''}
+
+              if [ ! -s '${internalToken}' ]; then
+                  ${exe} generate secret INTERNAL_TOKEN > '${internalToken}'
+              fi
+
+              chmod u+w '${runConfig}'
+              ${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
+              ${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
+              ${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
+
+              ${optionalString cfg.lfs.enable ''
+                ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
+              ''}
+
+              ${optionalString (cfg.database.passwordFile != null) ''
+                ${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
+              ''}
+
+              ${optionalString (cfg.mailerPasswordFile != null) ''
+                ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
+              ''}
+              chmod u-w '${runConfig}'
+            }
+            (umask 027; forgejo_setup)
+          ''}
+
+          # run migrations/init the database
+          ${exe} migrate
+
+          # update all hooks' binary paths
+          ${exe} admin regenerate hooks
+
+          # update command option in authorized_keys
+          if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
+          then
+            ${exe} admin regenerate keys
+          fi
+        '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.stateDir;
+        ExecStart = "${exe} web --pid /run/forgejo/forgejo.pid";
+        Restart = "always";
+        # Runtime directory and mode
+        RuntimeDirectory = "forgejo";
+        RuntimeDirectoryMode = "0755";
+        # Proc filesystem
+        ProcSubset = "pid";
+        ProtectProc = "invisible";
+        # Access write directories
+        ReadWritePaths = [ cfg.customDir cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
+        UMask = "0027";
+        # Capabilities
+        CapabilityBoundingSet = "";
+        # Security
+        NoNewPrivileges = true;
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+        PrivateMounts = true;
+        # System Call Filtering
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" "setrlimit" ];
+      };
+
+      environment = {
+        USER = cfg.user;
+        HOME = cfg.stateDir;
+        # `GITEA_` prefix until https://codeberg.org/forgejo/forgejo/issues/497
+        # is resolved.
+        GITEA_WORK_DIR = cfg.stateDir;
+        GITEA_CUSTOM = cfg.customDir;
+      };
+    };
+
+    services.openssh.settings.AcceptEnv = mkIf (!cfg.settings.START_SSH_SERVER or false) "GIT_PROTOCOL";
+
+    users.users = mkIf (cfg.user == "forgejo") {
+      forgejo = {
+        home = cfg.stateDir;
+        useDefaultShell = true;
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "forgejo") {
+      forgejo = { };
+    };
+
+    systemd.services.forgejo-dump = mkIf cfg.dump.enable {
+      description = "forgejo dump";
+      after = [ "forgejo.service" ];
+      path = [ cfg.package ];
+
+      environment = {
+        USER = cfg.user;
+        HOME = cfg.stateDir;
+        # `GITEA_` prefix until https://codeberg.org/forgejo/forgejo/issues/497
+        # is resolved.
+        GITEA_WORK_DIR = cfg.stateDir;
+        GITEA_CUSTOM = cfg.customDir;
+      };
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        ExecStart = "${exe} dump --type ${cfg.dump.type}" + optionalString (cfg.dump.file != null) " --file ${cfg.dump.file}";
+        WorkingDirectory = cfg.dump.backupDir;
+      };
+    };
+
+    systemd.timers.forgejo-dump = mkIf cfg.dump.enable {
+      description = "Forgejo dump timer";
+      partOf = [ "forgejo-dump.service" ];
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnCalendar = cfg.dump.interval;
+    };
+  };
+
+  meta.doc = ./forgejo.md;
+  meta.maintainers = with lib.maintainers; [ bendlas emilylange ];
+}
diff --git a/nixos/modules/services/misc/freeswitch.nix b/nixos/modules/services/misc/freeswitch.nix
index b8b81e5869448..a8f7b3d0c3aee 100644
--- a/nixos/modules/services/misc/freeswitch.nix
+++ b/nixos/modules/services/misc/freeswitch.nix
@@ -58,14 +58,7 @@ in {
           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 = lib.mdDoc ''
-          FreeSWITCH package.
-        '';
-      };
+      package = mkPackageOption pkgs "freeswitch" { };
     };
   };
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index f6ef2bb919107..d0135b2ba7acd 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -51,12 +51,7 @@ in
         description = lib.mdDoc "Enable Gitea Service.";
       };
 
-      package = mkOption {
-        default = pkgs.gitea;
-        type = types.package;
-        defaultText = literalExpression "pkgs.gitea";
-        description = lib.mdDoc "gitea derivation to use";
-      };
+      package = mkPackageOption pkgs "gitea" { };
 
       useWizard = mkOption {
         default = false;
@@ -239,6 +234,13 @@ in
         description = lib.mdDoc "Path to the git repositories.";
       };
 
+      camoHmacKeyFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/secrets/gitea/camoHmacKey";
+        description = lib.mdDoc "Path to a file containing the camo HMAC key.";
+      };
+
       mailerPasswordFile = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -246,6 +248,13 @@ in
         description = lib.mdDoc "Path to a file containing the SMTP password.";
       };
 
+      metricsTokenFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/secrets/gitea/metrics_token";
+        description = lib.mdDoc "Path to a file containing the metrics authentication token.";
+      };
+
       settings = mkOption {
         default = {};
         description = lib.mdDoc ''
@@ -387,6 +396,14 @@ in
       { assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
         message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
       }
+      { assertion = cfg.database.createDatabase && usePostgresql -> cfg.database.user == cfg.database.name;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.gitea.createDatabase` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
     ];
 
     services.gitea.settings = {
@@ -419,6 +436,10 @@ in
         LFS_JWT_SECRET = "#lfsjwtsecret#";
       };
 
+      camo = mkIf (cfg.camoHmacKeyFile != null) {
+        HMAC_KEY = "#hmackey#";
+      };
+
       session = {
         COOKIE_NAME = lib.mkDefault "session";
       };
@@ -433,6 +454,10 @@ in
         PASSWD = "#mailerpass#";
       };
 
+      metrics = mkIf (cfg.metricsTokenFile != null) {
+        TOKEN = "#metricstoken#";
+      };
+
       oauth2 = {
         JWT_SECRET = "#oauth2jwtsecret#";
       };
@@ -450,7 +475,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
@@ -556,9 +581,17 @@ in
               ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
             ''}
 
+            ${lib.optionalString (cfg.camoHmacKeyFile != null) ''
+              ${replaceSecretBin} '#hmackey#' '${cfg.camoHmacKeyFile}' '${runConfig}'
+            ''}
+
             ${lib.optionalString (cfg.mailerPasswordFile != null) ''
               ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
             ''}
+
+            ${lib.optionalString (cfg.metricsTokenFile != null) ''
+              ${replaceSecretBin} '#metricstoken#' '${cfg.metricsTokenFile}' '${runConfig}'
+            ''}
             chmod u-w '${runConfig}'
           }
           (umask 027; gitea_setup)
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index c5e38b4988298..6756d59cf367c 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -258,41 +258,17 @@ in {
         '';
       };
 
-      packages.gitlab = mkOption {
-        type = types.package;
-        default = pkgs.gitlab;
-        defaultText = literalExpression "pkgs.gitlab";
-        description = lib.mdDoc "Reference to the gitlab package";
-        example = literalExpression "pkgs.gitlab-ee";
+      packages.gitlab = mkPackageOption pkgs "gitlab" {
+        example = "gitlab-ee";
       };
 
-      packages.gitlab-shell = mkOption {
-        type = types.package;
-        default = pkgs.gitlab-shell;
-        defaultText = literalExpression "pkgs.gitlab-shell";
-        description = lib.mdDoc "Reference to the gitlab-shell package";
-      };
+      packages.gitlab-shell = mkPackageOption pkgs "gitlab-shell" { };
 
-      packages.gitlab-workhorse = mkOption {
-        type = types.package;
-        default = pkgs.gitlab-workhorse;
-        defaultText = literalExpression "pkgs.gitlab-workhorse";
-        description = lib.mdDoc "Reference to the gitlab-workhorse package";
-      };
+      packages.gitlab-workhorse = mkPackageOption pkgs "gitlab-workhorse" { };
 
-      packages.gitaly = mkOption {
-        type = types.package;
-        default = pkgs.gitaly;
-        defaultText = literalExpression "pkgs.gitaly";
-        description = lib.mdDoc "Reference to the gitaly package";
-      };
+      packages.gitaly = mkPackageOption pkgs "gitaly" { };
 
-      packages.pages = mkOption {
-        type = types.package;
-        default = pkgs.gitlab-pages;
-        defaultText = literalExpression "pkgs.gitlab-pages";
-        description = lib.mdDoc "Reference to the gitlab-pages package";
-      };
+      packages.pages = mkPackageOption pkgs "gitlab-pages" { };
 
       statePath = mkOption {
         type = types.str;
@@ -1088,6 +1064,11 @@ in {
         ''Support for container registries other than gitlab-container-registry has ended since GitLab 16.0.0 and is scheduled for removal in a future release.
           Please back up your data and migrate to the gitlab-container-registry package.''
       )
+      (mkIf
+        (versionAtLeast (getVersion cfg.packages.gitlab) "16.2.0" && versionOlder (getVersion cfg.packages.gitlab) "16.5.0")
+        ''GitLab instances created or updated between versions [15.11.0, 15.11.2] have an incorrect database schema.
+        Check the upstream documentation for a workaround: https://docs.gitlab.com/ee/update/versions/gitlab_16_changes.html#undefined-column-error-upgrading-to-162-or-later''
+      )
     ];
 
     assertions = [
@@ -1655,7 +1636,7 @@ in {
         Restart = "on-failure";
         WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
         ExecStart = concatStringsSep " " [
-          "${cfg.packages.gitlab.rubyEnv}/bin/puma"
+          "${cfg.packages.gitlab.rubyEnv}/bin/bundle" "exec" "puma"
           "-e production"
           "-C ${cfg.statePath}/config/puma.rb"
           "-w ${cfg.puma.workers}"
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index d607e92e5ec94..e31eeaf8a30a8 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -83,14 +83,7 @@ in
       description = lib.mdDoc "Specifies the path of the repository directory. If it does not exist, Gollum will create it on startup.";
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.gollum;
-      defaultText = literalExpression "pkgs.gollum";
-      description = lib.mdDoc ''
-        The package used in the service
-      '';
-    };
+    package = mkPackageOption pkgs "gollum" { };
 
     user = mkOption {
       type = types.str;
@@ -154,5 +147,5 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ erictapen bbenno joscha ];
+  meta.maintainers = with lib.maintainers; [ erictapen bbenno ];
 }
diff --git a/nixos/modules/services/misc/gpsd.nix b/nixos/modules/services/misc/gpsd.nix
index ce0f9bb3ba28c..5d2e806181dff 100644
--- a/nixos/modules/services/misc/gpsd.nix
+++ b/nixos/modules/services/misc/gpsd.nix
@@ -92,6 +92,16 @@ in {
         '';
       };
 
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "-r" "-s" "19200" ];
+        description = lib.mdDoc ''
+          A list of extra command line arguments to pass to gpsd.
+          Check gpsd(8) mangpage for possible arguments.
+        '';
+      };
+
     };
 
   };
@@ -117,12 +127,14 @@ in {
         Type = "forking";
         ExecStart = let
           devices = utils.escapeSystemdExecArgs cfg.devices;
+          extraArgs = utils.escapeSystemdExecArgs cfg.extraArgs;
         in ''
           ${pkgs.gpsd}/sbin/gpsd -D "${toString cfg.debugLevel}"  \
             -S "${toString cfg.port}"                             \
             ${optionalString cfg.readonly "-b"}                   \
             ${optionalString cfg.nowait "-n"}                     \
             ${optionalString cfg.listenany "-G"}                  \
+            ${extraArgs}                                          \
             ${devices}
         '';
       };
diff --git a/nixos/modules/services/misc/greenclip.nix b/nixos/modules/services/misc/greenclip.nix
index 45847af711413..ecfb864ab2b78 100644
--- a/nixos/modules/services/misc/greenclip.nix
+++ b/nixos/modules/services/misc/greenclip.nix
@@ -9,12 +9,7 @@ in {
   options.services.greenclip = {
     enable = mkEnableOption (lib.mdDoc "Greenclip daemon");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.haskellPackages.greenclip;
-      defaultText = literalExpression "pkgs.haskellPackages.greenclip";
-      description = lib.mdDoc "greenclip derivation to use.";
-    };
+    package = mkPackageOption pkgs [ "haskellPackages" "greenclip" ] { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/misc/guix/default.nix b/nixos/modules/services/misc/guix/default.nix
new file mode 100644
index 0000000000000..7174ff36b7090
--- /dev/null
+++ b/nixos/modules/services/misc/guix/default.nix
@@ -0,0 +1,406 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.guix;
+
+  package = cfg.package.override { inherit (cfg) stateDir storeDir; };
+
+  guixBuildUser = id: {
+    name = "guixbuilder${toString id}";
+    group = cfg.group;
+    extraGroups = [ cfg.group ];
+    createHome = false;
+    description = "Guix build user ${toString id}";
+    isSystemUser = true;
+  };
+
+  guixBuildUsers = numberOfUsers:
+    builtins.listToAttrs (map
+      (user: {
+        name = user.name;
+        value = user;
+      })
+      (builtins.genList guixBuildUser numberOfUsers));
+
+  # A set of Guix user profiles to be linked at activation. All of these should
+  # be default profiles managed by Guix CLI and the profiles are located in
+  # `${cfg.stateDir}/profiles/per-user/$USER/$PROFILE`.
+  guixUserProfiles = {
+    # The default Guix profile managed by `guix pull`. Take note this should be
+    # the profile with the most precedence in `PATH` env to let users use their
+    # updated versions of `guix` CLI.
+    "current-guix" = "\${XDG_CONFIG_HOME}/guix/current";
+
+    # The default Guix home profile. This profile contains more than exports
+    # such as an activation script at `$GUIX_HOME_PROFILE/activate`.
+    "guix-home" = "$HOME/.guix-home/profile";
+
+    # The default Guix profile similar to $HOME/.nix-profile from Nix.
+    "guix-profile" = "$HOME/.guix-profile";
+  };
+
+  # All of the Guix profiles to be used.
+  guixProfiles = lib.attrValues guixUserProfiles;
+
+  serviceEnv = {
+    GUIX_LOCPATH = "${cfg.stateDir}/guix/profiles/per-user/root/guix-profile/lib/locale";
+    LC_ALL = "C.UTF-8";
+  };
+in
+{
+  meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
+
+  options.services.guix = with lib; {
+    enable = mkEnableOption "Guix build daemon service";
+
+    group = mkOption {
+      type = types.str;
+      default = "guixbuild";
+      example = "guixbuild";
+      description = ''
+        The group of the Guix build user pool.
+      '';
+    };
+
+    nrBuildUsers = mkOption {
+      type = types.ints.unsigned;
+      description = ''
+        Number of Guix build users to be used in the build pool.
+      '';
+      default = 10;
+      example = 20;
+    };
+
+    extraArgs = mkOption {
+      type = with types; listOf str;
+      default = [ ];
+      example = [ "--max-jobs=4" "--debug" ];
+      description = ''
+        Extra flags to pass to the Guix daemon service.
+      '';
+    };
+
+    package = mkPackageOption pkgs "guix" {
+      extraDescription = ''
+        It should contain {command}`guix-daemon` and {command}`guix`
+        executable.
+      '';
+    };
+
+    storeDir = mkOption {
+      type = types.path;
+      default = "/gnu/store";
+      description = ''
+        The store directory where the Guix service will serve to/from. Take
+        note Guix cannot take advantage of substitutes if you set it something
+        other than {file}`/gnu/store` since most of the cached builds are
+        assumed to be in there.
+
+        ::: {.warning}
+        This will also recompile all packages because the normal cache no
+        longer applies.
+        :::
+      '';
+    };
+
+    stateDir = mkOption {
+      type = types.path;
+      default = "/var";
+      description = ''
+        The state directory where Guix service will store its data such as its
+        user-specific profiles, cache, and state files.
+
+        ::: {.warning}
+        Changing it to something other than the default will rebuild the
+        package.
+        :::
+      '';
+      example = "/gnu/var";
+    };
+
+    publish = {
+      enable = mkEnableOption "substitute server for your Guix store directory";
+
+      generateKeyPair = mkOption {
+        type = types.bool;
+        description = ''
+          Whether to generate signing keys in {file}`/etc/guix` which are
+          required to initialize a substitute server. Otherwise,
+          `--public-key=$FILE` and `--private-key=$FILE` can be passed in
+          {option}`services.guix.publish.extraArgs`.
+        '';
+        default = true;
+        example = false;
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 8181;
+        example = 8200;
+        description = ''
+          Port of the substitute server to listen on.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "guix-publish";
+        description = ''
+          Name of the user to change once the server is up.
+        '';
+      };
+
+      extraArgs = mkOption {
+        type = with types; listOf str;
+        description = ''
+          Extra flags to pass to the substitute server.
+        '';
+        default = [];
+        example = [
+          "--compression=zstd:6"
+          "--discover=no"
+        ];
+      };
+    };
+
+    gc = {
+      enable = mkEnableOption "automatic garbage collection service for Guix";
+
+      extraArgs = mkOption {
+        type = with types; listOf str;
+        default = [ ];
+        description = ''
+          List of arguments to be passed to {command}`guix gc`.
+
+          When given no option, it will try to collect all garbage which is
+          often inconvenient so it is recommended to set [some
+          options](https://guix.gnu.org/en/manual/en/html_node/Invoking-guix-gc.html).
+        '';
+        example = [
+          "--delete-generations=1m"
+          "--free-space=10G"
+          "--optimize"
+        ];
+      };
+
+      dates = lib.mkOption {
+        type = types.str;
+        default = "03:15";
+        example = "weekly";
+        description = ''
+          How often the garbage collection occurs. This takes the time format
+          from {manpage}`systemd.time(7)`.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    {
+      environment.systemPackages = [ package ];
+
+      users.users = guixBuildUsers cfg.nrBuildUsers;
+      users.groups.${cfg.group} = { };
+
+      # Guix uses Avahi (through guile-avahi) both for the auto-discovering and
+      # advertising substitute servers in the local network.
+      services.avahi.enable = lib.mkDefault true;
+      services.avahi.publish.enable = lib.mkDefault true;
+      services.avahi.publish.userServices = lib.mkDefault true;
+
+      # It's similar to Nix daemon so there's no question whether or not this
+      # should be sandboxed.
+      systemd.services.guix-daemon = {
+        environment = serviceEnv;
+        script = ''
+          ${lib.getExe' package "guix-daemon"} \
+            --build-users-group=${cfg.group} \
+            ${lib.escapeShellArgs cfg.extraArgs}
+        '';
+        serviceConfig = {
+          OOMPolicy = "continue";
+          RemainAfterExit = "yes";
+          Restart = "always";
+          TasksMax = 8192;
+        };
+        unitConfig.RequiresMountsFor = [
+          cfg.storeDir
+          cfg.stateDir
+        ];
+        wantedBy = [ "multi-user.target" ];
+      };
+
+      # This is based from Nix daemon socket unit from upstream Nix package.
+      # Guix build daemon has support for systemd-style socket activation.
+      systemd.sockets.guix-daemon = {
+        description = "Guix daemon socket";
+        before = [ "multi-user.target" ];
+        listenStreams = [ "${cfg.stateDir}/guix/daemon-socket/socket" ];
+        unitConfig.RequiresMountsFor = [ cfg.storeDir cfg.stateDir ];
+        wantedBy = [ "sockets.target" ];
+      };
+
+      systemd.mounts = [{
+        description = "Guix read-only store directory";
+        before = [ "guix-daemon.service" ];
+        what = cfg.storeDir;
+        where = cfg.storeDir;
+        type = "none";
+        options = "bind,ro";
+
+        unitConfig.DefaultDependencies = false;
+        wantedBy = [ "guix-daemon.service" ];
+      }];
+
+      # Make transferring files from one store to another easier with the usual
+      # case being of most substitutes from the official Guix CI instance.
+      system.activationScripts.guix-authorize-keys = ''
+        for official_server_keys in ${package}/share/guix/*.pub; do
+          ${lib.getExe' package "guix"} archive --authorize < $official_server_keys
+        done
+      '';
+
+      # Link the usual Guix profiles to the home directory. This is useful in
+      # ephemeral setups where only certain part of the filesystem is
+      # persistent (e.g., "Erase my darlings"-type of setup).
+      system.userActivationScripts.guix-activate-user-profiles.text = let
+        guixProfile = profile: "${cfg.stateDir}/guix/profiles/per-user/\${USER}/${profile}";
+        linkProfile = profile: location: let
+          userProfile = guixProfile profile;
+        in ''
+          [ -d "${userProfile}" ] && ln -sfn "${userProfile}" "${location}"
+        '';
+        linkProfileToPath = acc: profile: location: let
+          in acc + (linkProfile profile location);
+
+        # This should contain export-only Guix user profiles. The rest of it is
+        # handled manually in the activation script.
+        guixUserProfiles' = lib.attrsets.removeAttrs guixUserProfiles [ "guix-home" ];
+
+        linkExportsScript = lib.foldlAttrs linkProfileToPath "" guixUserProfiles';
+      in ''
+        # Don't export this please! It is only expected to be used for this
+        # activation script and nothing else.
+        XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
+
+        # Linking the usual Guix profiles into the home directory.
+        ${linkExportsScript}
+
+        # Activate all of the default Guix non-exports profiles manually.
+        ${linkProfile "guix-home" "$HOME/.guix-home"}
+        [ -L "$HOME/.guix-home" ] && "$HOME/.guix-home/activate"
+      '';
+
+      # GUIX_LOCPATH is basically LOCPATH but for Guix libc which in turn used by
+      # virtually every Guix-built packages. This is so that Guix-installed
+      # applications wouldn't use incompatible locale data and not touch its host
+      # system.
+      environment.sessionVariables.GUIX_LOCPATH = lib.makeSearchPath "lib/locale" guixProfiles;
+
+      # What Guix profiles export is very similar to Nix profiles so it is
+      # acceptable to list it here. Also, it is more likely that the user would
+      # want to use packages explicitly installed from Guix so we're putting it
+      # first.
+      environment.profiles = lib.mkBefore guixProfiles;
+    }
+
+    (lib.mkIf cfg.publish.enable {
+      systemd.services.guix-publish = {
+        description = "Guix remote store";
+        environment = serviceEnv;
+
+        # Mounts will be required by the daemon service anyways so there's no
+        # need add RequiresMountsFor= or something similar.
+        requires = [ "guix-daemon.service" ];
+        after = [ "guix-daemon.service" ];
+        partOf = [ "guix-daemon.service" ];
+
+        preStart = lib.mkIf cfg.publish.generateKeyPair ''
+          # Generate the keypair if it's missing.
+          [ -f "/etc/guix/signing-key.sec" ] && [ -f "/etc/guix/signing-key.pub" ] || \
+            ${lib.getExe' package "guix"} archive --generate-key || {
+              rm /etc/guix/signing-key.*;
+              ${lib.getExe' package "guix"} archive --generate-key;
+            }
+        '';
+        script = ''
+          ${lib.getExe' package "guix"} publish \
+            --user=${cfg.publish.user} --port=${builtins.toString cfg.publish.port} \
+            ${lib.escapeShellArgs cfg.publish.extraArgs}
+        '';
+
+        serviceConfig = {
+          Restart = "always";
+          RestartSec = 10;
+
+          ProtectClock = true;
+          ProtectHostname = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectControlGroups = true;
+          SystemCallFilter = [
+            "@system-service"
+            "@debug"
+            "@setuid"
+          ];
+
+          RestrictNamespaces = true;
+          RestrictAddressFamilies = [
+            "AF_UNIX"
+            "AF_INET"
+            "AF_INET6"
+          ];
+
+          # While the permissions can be set, it is assumed to be taken by Guix
+          # daemon service which it has already done the setup.
+          ConfigurationDirectory = "guix";
+
+          AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+          CapabilityBoundingSet = [
+            "CAP_NET_BIND_SERVICE"
+            "CAP_SETUID"
+            "CAP_SETGID"
+          ];
+        };
+        wantedBy = [ "multi-user.target" ];
+      };
+
+      users.users.guix-publish = lib.mkIf (cfg.publish.user == "guix-publish") {
+        description = "Guix publish user";
+        group = config.users.groups.guix-publish.name;
+        isSystemUser = true;
+      };
+      users.groups.guix-publish = {};
+    })
+
+    (lib.mkIf cfg.gc.enable {
+      # This service should be handled by root to collect all garbage by all
+      # users.
+      systemd.services.guix-gc = {
+        description = "Guix garbage collection";
+        startAt = cfg.gc.dates;
+        script = ''
+          ${lib.getExe' package "guix"} gc ${lib.escapeShellArgs cfg.gc.extraArgs}
+        '';
+
+        serviceConfig = {
+          Type = "oneshot";
+
+          PrivateDevices = true;
+          PrivateNetworks = true;
+          ProtectControlGroups = true;
+          ProtectHostname = true;
+          ProtectKernelTunables = true;
+          SystemCallFilter = [
+            "@default"
+            "@file-system"
+            "@basic-io"
+            "@system-service"
+          ];
+        };
+      };
+
+      systemd.timers.guix-gc.timerConfig.Persistent = true;
+    })
+  ]);
+}
diff --git a/nixos/modules/services/misc/heisenbridge.nix b/nixos/modules/services/misc/heisenbridge.nix
index 822a09d7cd4d6..d7ce9c605c9e8 100644
--- a/nixos/modules/services/misc/heisenbridge.nix
+++ b/nixos/modules/services/misc/heisenbridge.nix
@@ -25,14 +25,7 @@ in
   options.services.heisenbridge = {
     enable = mkEnableOption (lib.mdDoc "the Matrix to IRC bridge");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.heisenbridge;
-      defaultText = lib.literalExpression "pkgs.heisenbridge";
-      description = lib.mdDoc ''
-        Package of the application to run, exposed for overriding purposes.
-      '';
-    };
+    package = mkPackageOption pkgs "heisenbridge" { };
 
     homeserver = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/misc/homepage-dashboard.nix b/nixos/modules/services/misc/homepage-dashboard.nix
index e685712534333..07a09e2b6bbf5 100644
--- a/nixos/modules/services/misc/homepage-dashboard.nix
+++ b/nixos/modules/services/misc/homepage-dashboard.nix
@@ -12,7 +12,7 @@ in
     services.homepage-dashboard = {
       enable = lib.mkEnableOption (lib.mdDoc "Homepage Dashboard");
 
-      package = lib.mkPackageOptionMD pkgs "homepage-dashboard" { };
+      package = lib.mkPackageOption pkgs "homepage-dashboard" { };
 
       openFirewall = lib.mkOption {
         type = lib.types.bool;
diff --git a/nixos/modules/services/misc/input-remapper.nix b/nixos/modules/services/misc/input-remapper.nix
index 3f6d97f857381..5b9f16e019d8e 100644
--- a/nixos/modules/services/misc/input-remapper.nix
+++ b/nixos/modules/services/misc/input-remapper.nix
@@ -7,7 +7,7 @@ let cfg = config.services.input-remapper; in
   options = {
     services.input-remapper = {
       enable = mkEnableOption (lib.mdDoc "input-remapper, an easy to use tool to change the mapping of your input device buttons");
-      package = mkPackageOptionMD pkgs "input-remapper" { };
+      package = mkPackageOption pkgs "input-remapper" { };
       enableUdevRules = mkEnableOption (lib.mdDoc "udev rules added by input-remapper to handle hotplugged devices. Currently disabled by default due to https://github.com/sezanzeb/input-remapper/issues/140");
       serviceWantedBy = mkOption {
         default = [ "graphical.target" ];
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index b0edf0d18da46..c0bb0a575f01d 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -35,12 +35,7 @@ in
         description = lib.mdDoc "Group under which Jackett runs.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.jackett;
-        defaultText = literalExpression "pkgs.jackett";
-        description = lib.mdDoc "Jackett package to use.";
-      };
+      package = mkPackageOption pkgs "jackett" { };
     };
   };
 
diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix
index 2a4483199d7dd..7042b491ffa4e 100644
--- a/nixos/modules/services/misc/jellyfin.nix
+++ b/nixos/modules/services/misc/jellyfin.nix
@@ -16,14 +16,7 @@ in
         description = lib.mdDoc "User account under which Jellyfin runs.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.jellyfin;
-        defaultText = literalExpression "pkgs.jellyfin";
-        description = lib.mdDoc ''
-          Jellyfin package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "jellyfin" { };
 
       group = mkOption {
         type = types.str;
@@ -46,7 +39,8 @@ in
   config = mkIf cfg.enable {
     systemd.services.jellyfin = {
       description = "Jellyfin Media Server";
-      after = [ "network.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
 
       # This is mostly follows: https://github.com/jellyfin/jellyfin/blob/master/fedora/jellyfin.service
diff --git a/nixos/modules/services/misc/kafka.md b/nixos/modules/services/misc/kafka.md
new file mode 100644
index 0000000000000..370bb3b482d2c
--- /dev/null
+++ b/nixos/modules/services/misc/kafka.md
@@ -0,0 +1,63 @@
+# Apache Kafka {#module-services-apache-kafka}
+
+[Apache Kafka](https://kafka.apache.org/) is an open-source distributed event
+streaming platform
+
+## Basic Usage {#module-services-apache-kafka-basic-usage}
+
+The Apache Kafka service is configured almost exclusively through its
+[settings](#opt-services.apache-kafka.settings) option, with each attribute
+corresponding to the [upstream configuration
+manual](https://kafka.apache.org/documentation/#configuration) broker settings.
+
+## KRaft {#module-services-apache-kafka-kraft}
+
+Unlike in Zookeeper mode, Kafka in
+[KRaft](https://kafka.apache.org/documentation/#kraft) mode requires each log
+dir to be "formatted" (which means a cluster-specific a metadata file must
+exist in each log dir)
+
+The upstream intention is for users to execute the [storage
+tool](https://kafka.apache.org/documentation/#kraft_storage) to achieve this,
+but this module contains a few extra options to automate this:
+
+- [](#opt-services.apache-kafka.clusterId)
+- [](#opt-services.apache-kafka.formatLogDirs)
+- [](#opt-services.apache-kafka.formatLogDirsIgnoreFormatted)
+
+## Migrating to settings {#module-services-apache-kafka-migrating-to-settings}
+
+Migrating a cluster to the new `settings`-based changes requires adapting removed options to the corresponding upstream settings.
+
+This means that the upstream [Broker Configs documentation](https://kafka.apache.org/documentation/#brokerconfigs) should be followed closely.
+
+Note that dotted options in the upstream docs do _not_ correspond to nested Nix attrsets, but instead as quoted top level `settings` attributes, as in `services.apache-kafka.settings."broker.id"`, *NOT* `services.apache-kafka.settings.broker.id`.
+
+Care should be taken, especially when migrating clusters from the old module, to ensure that the same intended configuration is reproduced faithfully via `settings`.
+
+To assist in the comparison, the final config can be inspected by building the config file itself, ie. with: `nix-build <nixpkgs/nixos> -A config.services.apache-kafka.configFiles.serverProperties`.
+
+Notable changes to be aware of include:
+
+- Removal of `services.apache-kafka.extraProperties` and `services.apache-kafka.serverProperties`
+  - Translate using arbitrary properties using [](#opt-services.apache-kafka.settings)
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs)
+  - The intention is for all broker properties to be fully representable via [](#opt-services.apache-kafka.settings).
+  - If this is not the case, please do consider raising an issue.
+  - Until it can be remedied, you *can* bail out by using [](#opt-services.apache-kafka.configFiles.serverProperties) to the path of a fully rendered properties file.
+
+- Removal of `services.apache-kafka.hostname` and `services.apache-kafka.port`
+  - Translate using: `services.apache-kafka.settings.listeners`
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs_listeners)
+
+- Removal of `services.apache-kafka.logDirs`
+  - Translate using: `services.apache-kafka.settings."log.dirs"`
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs_log.dirs)
+
+- Removal of `services.apache-kafka.brokerId`
+  - Translate using: `services.apache-kafka.settings."broker.id"`
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs_broker.id)
+
+- Removal of `services.apache-kafka.zookeeper`
+  - Translate using: `services.apache-kafka.settings."zookeeper.connect"`
+  - [Upstream docs](https://kafka.apache.org/documentation.html#brokerconfigs_zookeeper.connect)
diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix
index 67a217c994e45..a0eb409599b59 100644
--- a/nixos/modules/services/misc/klipper.nix
+++ b/nixos/modules/services/misc/klipper.nix
@@ -16,12 +16,7 @@ in
     services.klipper = {
       enable = mkEnableOption (lib.mdDoc "Klipper, the 3D printer firmware");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.klipper;
-        defaultText = literalExpression "pkgs.klipper";
-        description = lib.mdDoc "The Klipper package.";
-      };
+      package = mkPackageOption pkgs "klipper" { };
 
       logFile = mkOption {
         type = types.nullOr types.path;
@@ -111,11 +106,11 @@ in
           (submodule {
             options = {
               enable = mkEnableOption (lib.mdDoc ''
-                building of firmware for manual flashing.
+                building of firmware for manual flashing
               '');
               enableKlipperFlash = mkEnableOption (lib.mdDoc ''
                 flashings scripts for firmware. This will add `klipper-flash-$mcu` scripts to your environment which can be called to flash the firmware.
-                Please check the configs at [klipper](https://github.com/Klipper3d/klipper/tree/master/config) whether your board supports flashing via `make flash`.
+                Please check the configs at [klipper](https://github.com/Klipper3d/klipper/tree/master/config) whether your board supports flashing via `make flash`
               '');
               serial = mkOption {
                 type = types.nullOr path;
diff --git a/nixos/modules/services/misc/libreddit.nix b/nixos/modules/services/misc/libreddit.nix
index fd58928d28219..02d71c198e783 100644
--- a/nixos/modules/services/misc/libreddit.nix
+++ b/nixos/modules/services/misc/libreddit.nix
@@ -15,12 +15,7 @@ in
     services.libreddit = {
       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.";
-      };
+      package = mkPackageOption pkgs "libreddit" { };
 
       address = mkOption {
         default = "0.0.0.0";
diff --git a/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
index 92b00054bdff9..4dc0fc63863b7 100644
--- a/nixos/modules/services/misc/lidarr.nix
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -16,12 +16,7 @@ in
         description = lib.mdDoc "The directory where Lidarr stores its data files.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.lidarr;
-        defaultText = literalExpression "pkgs.lidarr";
-        description = lib.mdDoc "The Lidarr package to use";
-      };
+      package = mkPackageOption pkgs "lidarr" { };
 
       openFirewall = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/misc/llama-cpp.nix b/nixos/modules/services/misc/llama-cpp.nix
new file mode 100644
index 0000000000000..4d76456fb2fd5
--- /dev/null
+++ b/nixos/modules/services/misc/llama-cpp.nix
@@ -0,0 +1,111 @@
+{ config, lib, pkgs, utils, ... }:
+
+let
+  cfg = config.services.llama-cpp;
+in {
+
+  options = {
+
+    services.llama-cpp = {
+      enable = lib.mkEnableOption "LLaMA C++ server";
+
+      package = lib.mkPackageOption pkgs "llama-cpp" { };
+
+      model = lib.mkOption {
+        type = lib.types.path;
+        example = "/models/mistral-instruct-7b/ggml-model-q4_0.gguf";
+        description = "Model path.";
+      };
+
+      extraFlags = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        description = "Extra flags passed to llama-cpp-server.";
+        example = ["-c" "4096" "-ngl" "32" "--numa"];
+        default = [];
+      };
+
+      host = lib.mkOption {
+        type = lib.types.str;
+        default = "127.0.0.1";
+        example = "0.0.0.0";
+        description = "IP address the LLaMA C++ server listens on.";
+      };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 8080;
+        description = "Listen port for LLaMA C++ server.";
+      };
+
+      openFirewall = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = "Open ports in the firewall for LLaMA C++ server.";
+      };
+    };
+
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd.services.llama-cpp = {
+      description = "LLaMA C++ server";
+      after = ["network.target"];
+      wantedBy = ["multi-user.target"];
+
+      serviceConfig = {
+        Type = "idle";
+        KillSignal = "SIGINT";
+        ExecStart = "${cfg.package}/bin/llama-cpp-server --log-disable --host ${cfg.host} --port ${builtins.toString cfg.port} -m ${cfg.model} ${utils.escapeSystemdExecArgs cfg.extraFlags}";
+        Restart = "on-failure";
+        RestartSec = 300;
+
+        # for GPU acceleration
+        PrivateDevices = false;
+
+        # hardening
+        DynamicUser = true;
+        CapabilityBoundingSet = "";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        NoNewPrivileges = true;
+        PrivateMounts = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = "strict";
+        MemoryDenyWriteExecute = true;
+        LockPersonality = true;
+        RemoveIPC = true;
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+          "~@resources"
+        ];
+        SystemCallErrorNumber = "EPERM";
+        ProtectProc = "invisible";
+        ProtectHostname = true;
+        ProcSubset = "pid";
+      };
+    };
+
+    networking.firewall = lib.mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ newam ];
+}
diff --git a/nixos/modules/services/misc/mbpfan.nix b/nixos/modules/services/misc/mbpfan.nix
index e75c352541438..ef56ea49d1a95 100644
--- a/nixos/modules/services/misc/mbpfan.nix
+++ b/nixos/modules/services/misc/mbpfan.nix
@@ -11,12 +11,7 @@ in {
   options.services.mbpfan = {
     enable = mkEnableOption (lib.mdDoc "mbpfan, fan controller daemon for Apple Macs and MacBooks");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.mbpfan;
-      defaultText = literalExpression "pkgs.mbpfan";
-      description = lib.mdDoc "The package used for the mbpfan daemon.";
-    };
+    package = mkPackageOption pkgs "mbpfan" { };
 
     verbose = mkOption {
       type = types.bool;
@@ -26,7 +21,7 @@ in {
 
     aggressive = mkOption {
       type = types.bool;
-      default = false;
+      default = true;
       description = lib.mdDoc "If true, favors higher default fan speeds.";
     };
 
@@ -38,17 +33,20 @@ in {
 
         options.general.low_temp = mkOption {
           type = types.int;
-          default = 63;
+          default = (if cfg.aggressive then 55 else 63);
+          defaultText = literalExpression "55";
           description = lib.mdDoc "If temperature is below this, fans will run at minimum speed.";
         };
         options.general.high_temp = mkOption {
           type = types.int;
-          default = 66;
+          default = (if cfg.aggressive then 58 else 66);
+          defaultText = literalExpression "58";
           description = lib.mdDoc "If temperature is above this, fan speed will gradually increase.";
         };
         options.general.max_temp = mkOption {
           type = types.int;
-          default = 86;
+          default = (if cfg.aggressive then 78 else 86);
+          defaultText = literalExpression "78";
           description = lib.mdDoc "If temperature is above this, fans will run at maximum speed.";
         };
         options.general.polling_interval = mkOption {
@@ -70,13 +68,6 @@ in {
   ];
 
   config = mkIf cfg.enable {
-    services.mbpfan.settings = mkIf cfg.aggressive {
-      general.min_fan1_speed = mkDefault 2000;
-      general.low_temp = mkDefault 55;
-      general.high_temp = mkDefault 58;
-      general.max_temp = mkDefault 70;
-    };
-
     boot.kernelModules = [ "coretemp" "applesmc" ];
     environment.systemPackages = [ cfg.package ];
     environment.etc."mbpfan.conf".source = settingsFile;
@@ -86,6 +77,7 @@ in {
       wantedBy = [ "sysinit.target" ];
       after = [ "syslog.target" "sysinit.target" ];
       restartTriggers = [ config.environment.etc."mbpfan.conf".source ];
+
       serviceConfig = {
         Type = "simple";
         ExecStart = "${cfg.package}/bin/mbpfan -f${verbose}";
diff --git a/nixos/modules/services/misc/mediatomb.nix b/nixos/modules/services/misc/mediatomb.nix
index 632b7caaac403..03235e9a12655 100644
--- a/nixos/modules/services/misc/mediatomb.nix
+++ b/nixos/modules/services/misc/mediatomb.nix
@@ -186,7 +186,7 @@ let
   defaultFirewallRules = {
     # udp 1900 port needs to be opened for SSDP (not configurable within
     # mediatomb/gerbera) cf.
-    # http://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup
+    # https://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup
     allowedUDPPorts = [ 1900 cfg.port ];
     allowedTCPPorts = [ cfg.port ];
   };
@@ -215,14 +215,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.gerbera;
-        defaultText = literalExpression "pkgs.gerbera";
-        description = lib.mdDoc ''
-          Underlying package to be used with the module.
-        '';
-      };
+      package = mkPackageOption pkgs "gerbera" { };
 
       ps3Support = mkOption {
         type = types.bool;
@@ -364,6 +357,7 @@ in {
       description = "${cfg.serverName} media Server";
       # Gerbera might fail if the network interface is not available on startup
       # https://github.com/gerbera/gerbera/issues/1324
+      wants = [ "network-online.target" ];
       after = [ "network.target" "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig.ExecStart = "${binaryCommand} --port ${toString cfg.port} ${interfaceFlag} ${configFlag} --home ${cfg.dataDir}";
diff --git a/nixos/modules/services/misc/metabase.nix b/nixos/modules/services/misc/metabase.nix
index 883fa0b959116..5fc18e27eaae4 100644
--- a/nixos/modules/services/misc/metabase.nix
+++ b/nixos/modules/services/misc/metabase.nix
@@ -77,6 +77,7 @@ in {
     systemd.services.metabase = {
       description = "Metabase server";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       environment = {
         MB_PLUGINS_DIR = "${dataDir}/plugins";
diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix
index 7e306d718e082..4e419aafa990b 100644
--- a/nixos/modules/services/misc/moonraker.nix
+++ b/nixos/modules/services/misc/moonraker.nix
@@ -1,8 +1,8 @@
 { config, lib, options, pkgs, ... }:
 with lib;
 let
-  pkg = pkgs.moonraker;
   cfg = config.services.moonraker;
+  pkg = cfg.package;
   opt = options.services.moonraker;
   format = pkgs.formats.ini {
     # https://github.com/NixOS/nixpkgs/pull/121613#issuecomment-885241996
@@ -18,6 +18,11 @@ in {
     services.moonraker = {
       enable = mkEnableOption (lib.mdDoc "Moonraker, an API web server for Klipper");
 
+      package = mkPackageOption pkgs "moonraker" {
+        nullable = true;
+        example = "moonraker.override { useGpiod = true; }";
+      };
+
       klipperSocket = mkOption {
         type = types.path;
         default = config.services.klipper.apiSocket;
@@ -98,7 +103,7 @@ in {
 
   config = mkIf cfg.enable {
     warnings = []
-      ++ optional (cfg.settings ? update_manager)
+      ++ optional (cfg.settings.update_manager.enable_system_updates or false)
         ''Enabling update_manager is not supported on NixOS and will lead to non-removable warnings in some clients.''
       ++ optional (cfg.configDir != null)
         ''
@@ -181,6 +186,12 @@ in {
       };
     };
 
+    # set this to false, otherwise we'll get a warning indicating that `/etc/klipper.cfg`
+    # is not located in the moonraker config directory.
+    services.moonraker.settings = lib.mkIf (!config.services.klipper.mutableConfig) {
+      file_manager.check_klipper_config_path = false;
+    };
+
     security.polkit.extraConfig = lib.optionalString cfg.allowSystemControl ''
       // nixos/moonraker: Allow Moonraker to perform system-level operations
       //
diff --git a/nixos/modules/services/misc/nitter.nix b/nixos/modules/services/misc/nitter.nix
index 77f5459d117cc..d2cf7c0de2b77 100644
--- a/nixos/modules/services/misc/nitter.nix
+++ b/nixos/modules/services/misc/nitter.nix
@@ -54,12 +54,7 @@ in
     services.nitter = {
       enable = mkEnableOption (lib.mdDoc "Nitter");
 
-      package = mkOption {
-        default = pkgs.nitter;
-        type = types.package;
-        defaultText = literalExpression "pkgs.nitter";
-        description = lib.mdDoc "The nitter derivation to use.";
-      };
+      package = mkPackageOption pkgs "nitter" { };
 
       server = {
         address = mkOption {
@@ -309,6 +304,23 @@ in
         '';
       };
 
+      guestAccounts = mkOption {
+        type = types.path;
+        default = "/var/lib/nitter/guest_accounts.jsonl";
+        description = lib.mdDoc ''
+          Path to the guest accounts file.
+
+          This file contains a list of guest accounts that can be used to
+          access the instance without logging in. The file is in JSONL format,
+          where each line is a JSON object with the following fields:
+
+          {"oauth_token":"some_token","oauth_token_secret":"some_secret_key"}
+
+          See https://github.com/zedeus/nitter/wiki/Guest-Account-Branch-Deployment
+          for more information on guest accounts and how to generate them.
+        '';
+      };
+
       redisCreateLocally = mkOption {
         type = types.bool;
         default = true;
@@ -338,8 +350,12 @@ in
         after = [ "network-online.target" ];
         serviceConfig = {
           DynamicUser = true;
+          LoadCredential="guestAccountsFile:${cfg.guestAccounts}";
           StateDirectory = "nitter";
-          Environment = [ "NITTER_CONF_FILE=/var/lib/nitter/nitter.conf" ];
+          Environment = [
+            "NITTER_CONF_FILE=/var/lib/nitter/nitter.conf"
+            "NITTER_ACCOUNTS_FILE=%d/guestAccountsFile"
+          ];
           # Some parts of Nitter expect `public` folder in working directory,
           # see https://github.com/zedeus/nitter/issues/414
           WorkingDirectory = "${cfg.package}/share/nitter";
diff --git a/nixos/modules/services/misc/nix-gc.nix b/nixos/modules/services/misc/nix-gc.nix
index 97596d28cd89b..de6bd76c7eb9d 100644
--- a/nixos/modules/services/misc/nix-gc.nix
+++ b/nixos/modules/services/misc/nix-gc.nix
@@ -1,7 +1,5 @@
 { config, lib, ... }:
 
-with lib;
-
 let
   cfg = config.nix.gc;
 in
@@ -14,14 +12,14 @@ in
 
     nix.gc = {
 
-      automatic = mkOption {
+      automatic = lib.mkOption {
         default = false;
-        type = types.bool;
+        type = lib.types.bool;
         description = lib.mdDoc "Automatically run the garbage collector at a specific time.";
       };
 
-      dates = mkOption {
-        type = types.str;
+      dates = lib.mkOption {
+        type = lib.types.singleLineStr;
         default = "03:15";
         example = "weekly";
         description = lib.mdDoc ''
@@ -33,9 +31,9 @@ in
         '';
       };
 
-      randomizedDelaySec = mkOption {
+      randomizedDelaySec = lib.mkOption {
         default = "0";
-        type = types.str;
+        type = lib.types.singleLineStr;
         example = "45min";
         description = lib.mdDoc ''
           Add a randomized delay before each garbage collection.
@@ -45,9 +43,9 @@ in
         '';
       };
 
-      persistent = mkOption {
+      persistent = lib.mkOption {
         default = true;
-        type = types.bool;
+        type = lib.types.bool;
         example = false;
         description = lib.mdDoc ''
           Takes a boolean argument. If true, the time when the service
@@ -61,10 +59,10 @@ in
         '';
       };
 
-      options = mkOption {
+      options = lib.mkOption {
         default = "";
         example = "--max-freed $((64 * 1024**3))";
-        type = types.str;
+        type = lib.types.singleLineStr;
         description = lib.mdDoc ''
           Options given to {file}`nix-collect-garbage` when the
           garbage collector is run automatically.
@@ -89,7 +87,8 @@ in
     systemd.services.nix-gc = lib.mkIf config.nix.enable {
       description = "Nix Garbage Collector";
       script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}";
-      startAt = optional cfg.automatic cfg.dates;
+      serviceConfig.Type = "oneshot";
+      startAt = lib.optional cfg.automatic cfg.dates;
     };
 
     systemd.timers.nix-gc = lib.mkIf cfg.automatic {
diff --git a/nixos/modules/services/misc/nix-ssh-serve.nix b/nixos/modules/services/misc/nix-ssh-serve.nix
index b656692ca01cd..cf9d6339c69b7 100644
--- a/nixos/modules/services/misc/nix-ssh-serve.nix
+++ b/nixos/modules/services/misc/nix-ssh-serve.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 let cfg = config.nix.sshServe;
@@ -46,7 +46,7 @@ in {
       description = "Nix SSH store user";
       isSystemUser = true;
       group = "nix-ssh";
-      useDefaultShell = true;
+      shell = pkgs.bashInteractive;
     };
     users.groups.nix-ssh = {};
 
diff --git a/nixos/modules/services/misc/ntfy-sh.nix b/nixos/modules/services/misc/ntfy-sh.nix
index 8fc1df93afb16..b8b0772401156 100644
--- a/nixos/modules/services/misc/ntfy-sh.nix
+++ b/nixos/modules/services/misc/ntfy-sh.nix
@@ -12,12 +12,7 @@ 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.";
-    };
+    package = mkPackageOption pkgs "ntfy-sh" { };
 
     user = mkOption {
       default = "ntfy-sh";
@@ -84,12 +79,6 @@ in
         cache-file = mkDefault "/var/lib/ntfy-sh/cache-file.db";
       };
 
-      systemd.tmpfiles.rules = [
-        "f ${cfg.settings.auth-file} 0600 ${cfg.user} ${cfg.group} - -"
-        "d ${cfg.settings.attachment-cache-dir} 0700 ${cfg.user} ${cfg.group} - -"
-        "f ${cfg.settings.cache-file} 0600 ${cfg.user} ${cfg.group} - -"
-      ];
-
       systemd.services.ntfy-sh = {
         description = "Push notifications server";
 
diff --git a/nixos/modules/services/misc/nzbhydra2.nix b/nixos/modules/services/misc/nzbhydra2.nix
index 47d08135f57ec..536a4e4b00758 100644
--- a/nixos/modules/services/misc/nzbhydra2.nix
+++ b/nixos/modules/services/misc/nzbhydra2.nix
@@ -22,12 +22,7 @@ in {
           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 = lib.mdDoc "NZBHydra2 package to use.";
-      };
+      package = mkPackageOption pkgs "nzbhydra2" { };
     };
   };
 
diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix
new file mode 100644
index 0000000000000..d9359d2b5cd44
--- /dev/null
+++ b/nixos/modules/services/misc/ollama.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }: let
+
+  cfg = config.services.ollama;
+
+in {
+
+  options = {
+    services.ollama = {
+      enable = lib.mkEnableOption (
+        lib.mdDoc "Server for local large language models"
+      );
+      listenAddress = lib.mkOption {
+        type = lib.types.str;
+        default = "127.0.0.1:11434";
+        description = lib.mdDoc ''
+          Specifies the bind address on which the ollama server HTTP interface listens.
+        '';
+      };
+      package = lib.mkPackageOption pkgs "ollama" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd = {
+      services.ollama = {
+        wantedBy = [ "multi-user.target" ];
+        description = "Server for local large language models";
+        after = [ "network.target" ];
+        environment = {
+          HOME = "%S/ollama";
+          OLLAMA_MODELS = "%S/ollama/models";
+          OLLAMA_HOST = cfg.listenAddress;
+        };
+        serviceConfig = {
+          ExecStart = "${lib.getExe cfg.package} serve";
+          WorkingDirectory = "/var/lib/ollama";
+          StateDirectory = [ "ollama" ];
+          DynamicUser = true;
+        };
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ onny ];
+
+}
diff --git a/nixos/modules/services/misc/packagekit.nix b/nixos/modules/services/misc/packagekit.nix
index f3e6bf50e9b2f..5a0d314d25cd6 100644
--- a/nixos/modules/services/misc/packagekit.nix
+++ b/nixos/modules/services/misc/packagekit.nix
@@ -40,9 +40,9 @@ in
 
   options.services.packagekit = {
     enable = mkEnableOption (lib.mdDoc ''
-      PackageKit provides a cross-platform D-Bus abstraction layer for
+      PackageKit, a cross-platform D-Bus abstraction layer for
       installing software. Software utilizing PackageKit can install
-      software regardless of the package manager.
+      software regardless of the package manager
     '');
 
     settings = mkOption {
diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix
index 74a3b49ac9a67..ca34a327dbdfa 100644
--- a/nixos/modules/services/misc/paperless.nix
+++ b/nixos/modules/services/misc/paperless.nix
@@ -10,7 +10,7 @@ let
   defaultFont = "${pkgs.liberation_ttf}/share/fonts/truetype/LiberationSerif-Regular.ttf";
 
   # Don't start a redis instance if the user sets a custom redis connection
-  enableRedis = !hasAttr "PAPERLESS_REDIS" cfg.extraConfig;
+  enableRedis = !(cfg.settings ? PAPERLESS_REDIS);
   redisServer = config.services.redis.servers.paperless;
 
   env = {
@@ -24,9 +24,11 @@ let
     PAPERLESS_TIME_ZONE = config.time.timeZone;
   } // optionalAttrs enableRedis {
     PAPERLESS_REDIS = "unix://${redisServer.unixSocket}";
-  } // (
-    lib.mapAttrs (_: toString) cfg.extraConfig
-  );
+  } // (lib.mapAttrs (_: s:
+    if (lib.isAttrs s || lib.isList s) then builtins.toJSON s
+    else if lib.isBool s then lib.boolToString s
+    else toString s
+  ) cfg.settings);
 
   manage = pkgs.writeShellScript "manage" ''
     set -o allexport # Export the following env vars
@@ -36,18 +38,7 @@ let
 
   # Secure the services
   defaultServiceConfig = {
-    TemporaryFileSystem = "/:ro";
-    BindReadOnlyPaths = [
-      "/nix/store"
-      "-/etc/resolv.conf"
-      "-/etc/nsswitch.conf"
-      "-/etc/hosts"
-      "-/etc/localtime"
-      "-/etc/ssl/certs"
-      "-/etc/static/ssl/certs"
-      "-/run/postgresql"
-    ] ++ (optional enableRedis redisServer.unixSocket);
-    BindPaths = [
+    ReadWritePaths = [
       cfg.consumptionDir
       cfg.dataDir
       cfg.mediaDir
@@ -66,11 +57,9 @@ let
     PrivateUsers = true;
     ProtectClock = true;
     # Breaks if the home dir of the user is in /home
-    # Also does not add much value in combination with the TemporaryFileSystem.
     # ProtectHome = true;
     ProtectHostname = true;
-    # Would re-mount paths ignored by temporary root
-    #ProtectSystem = "strict";
+    ProtectSystem = "strict";
     ProtectControlGroups = true;
     ProtectKernelLogs = true;
     ProtectKernelModules = true;
@@ -95,6 +84,7 @@ in
 
   imports = [
     (mkRenamedOptionModule [ "services" "paperless-ng" ] [ "services" "paperless" ])
+    (mkRenamedOptionModule [ "services" "paperless" "extraConfig" ] [ "services" "paperless" "settings" ])
   ];
 
   options.services.paperless = {
@@ -173,32 +163,30 @@ in
       description = lib.mdDoc "Web interface port.";
     };
 
-    # FIXME this should become an RFC42-style settings attr
-    extraConfig = mkOption {
-      type = types.attrs;
+    settings = mkOption {
+      type = lib.types.submodule {
+        freeformType = with lib.types; attrsOf (let
+          typeList = [ bool float int str path package ];
+        in oneOf (typeList ++ [ (listOf (oneOf typeList)) (attrsOf (oneOf typeList)) ]));
+      };
       default = { };
       description = lib.mdDoc ''
         Extra paperless config options.
 
-        See [the documentation](https://docs.paperless-ngx.com/configuration/)
-        for available options.
+        See [the documentation](https://docs.paperless-ngx.com/configuration/) for available options.
 
-        Note that some options such as `PAPERLESS_CONSUMER_IGNORE_PATTERN` expect JSON values. Use `builtins.toJSON` to ensure proper quoting.
+        Note that some settings such as `PAPERLESS_CONSUMER_IGNORE_PATTERN` expect JSON values.
+        Settings declared as lists or attrsets will automatically be serialised into JSON strings for your convenience.
       '';
-      example = literalExpression ''
-        {
-          PAPERLESS_OCR_LANGUAGE = "deu+eng";
-
-          PAPERLESS_DBHOST = "/run/postgresql";
-
-          PAPERLESS_CONSUMER_IGNORE_PATTERN = builtins.toJSON [ ".DS_STORE/*" "desktop.ini" ];
-
-          PAPERLESS_OCR_USER_ARGS = builtins.toJSON {
-            optimize = 1;
-            pdfa_image_compression = "lossless";
-          };
+      example = {
+        PAPERLESS_OCR_LANGUAGE = "deu+eng";
+        PAPERLESS_DBHOST = "/run/postgresql";
+        PAPERLESS_CONSUMER_IGNORE_PATTERN = [ ".DS_STORE/*" "desktop.ini" ];
+        PAPERLESS_OCR_USER_ARGS = {
+          optimize = 1;
+          pdfa_image_compression = "lossless";
         };
-      '';
+      };
     };
 
     user = mkOption {
@@ -207,12 +195,7 @@ in
       description = lib.mdDoc "User under which Paperless runs.";
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.paperless-ngx;
-      defaultText = literalExpression "pkgs.paperless-ngx";
-      description = lib.mdDoc "The Paperless package to use.";
-    };
+    package = mkPackageOption pkgs "paperless-ngx" { };
   };
 
   config = mkIf cfg.enable {
@@ -314,22 +297,12 @@ in
       wantedBy = [ "paperless-scheduler.service" ];
       before = [ "paperless-scheduler.service" ];
       after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
         Type = "oneshot";
         # Enable internet access
         PrivateNetwork = false;
-        # Restrict write access
-        BindPaths = [];
-        BindReadOnlyPaths = [
-          "/nix/store"
-          "-/etc/resolv.conf"
-          "-/etc/nsswitch.conf"
-          "-/etc/ssl/certs"
-          "-/etc/static/ssl/certs"
-          "-/etc/hosts"
-          "-/etc/localtime"
-        ];
         ExecStart = let pythonWithNltk = pkg.python.withPackages (ps: [ ps.nltk ]); in ''
           ${pythonWithNltk}/bin/python -m nltk.downloader -d '${nltkDir}' punkt snowball_data stopwords
         '';
@@ -356,12 +329,28 @@ in
       # during migrations
       bindsTo = [ "paperless-scheduler.service" ];
       after = [ "paperless-scheduler.service" ];
+      # Setup PAPERLESS_SECRET_KEY.
+      # If this environment variable is left unset, paperless-ngx defaults
+      # to a well-known value, which is insecure.
+      script = let
+        secretKeyFile = "${cfg.dataDir}/nixos-paperless-secret-key";
+      in ''
+        if [[ ! -f '${secretKeyFile}' ]]; then
+          (
+            umask 0377
+            tr -dc A-Za-z0-9 < /dev/urandom | head -c64 | ${pkgs.moreutils}/bin/sponge '${secretKeyFile}'
+          )
+        fi
+        export PAPERLESS_SECRET_KEY=$(cat '${secretKeyFile}')
+        if [[ ! $PAPERLESS_SECRET_KEY ]]; then
+          echo "PAPERLESS_SECRET_KEY is empty, refusing to start."
+          exit 1
+        fi
+        exec ${pkg.python.pkgs.gunicorn}/bin/gunicorn \
+          -c ${pkg}/lib/paperless-ngx/gunicorn.conf.py paperless.asgi:application
+      '';
       serviceConfig = defaultServiceConfig // {
         User = cfg.user;
-        ExecStart = ''
-          ${pkg.python.pkgs.gunicorn}/bin/gunicorn \
-            -c ${pkg}/lib/paperless-ngx/gunicorn.conf.py paperless.asgi:application
-        '';
         Restart = "on-failure";
 
         # gunicorn needs setuid, liblapack needs mbind
@@ -373,7 +362,6 @@ in
         CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
       };
       environment = env // {
-        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.
diff --git a/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index 7fc76028c02a6..164801605713b 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -93,13 +93,10 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.plex;
-        defaultText = literalExpression "pkgs.plex";
-        description = lib.mdDoc ''
-          The Plex package to use. Plex subscribers may wish to use their own
-          package here, pointing to subscriber-only server versions.
+      package = mkPackageOption pkgs "plex" {
+        extraDescription = ''
+          Plex subscribers may wish to use their own package here,
+          pointing to subscriber-only server versions.
         '';
       };
     };
diff --git a/nixos/modules/services/misc/polaris.nix b/nixos/modules/services/misc/polaris.nix
index 70f097f028406..83da486083b42 100644
--- a/nixos/modules/services/misc/polaris.nix
+++ b/nixos/modules/services/misc/polaris.nix
@@ -13,7 +13,7 @@ in
     services.polaris = {
       enable = mkEnableOption (lib.mdDoc "Polaris Music Server");
 
-      package = mkPackageOptionMD pkgs "polaris" { };
+      package = mkPackageOption pkgs "polaris" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/misc/portunus.nix b/nixos/modules/services/misc/portunus.nix
index d188819869702..47af24f024cdf 100644
--- a/nixos/modules/services/misc/portunus.nix
+++ b/nixos/modules/services/misc/portunus.nix
@@ -26,12 +26,7 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.portunus;
-      defaultText = lib.literalExpression "pkgs.portunus";
-      description = lib.mdDoc "The Portunus package to use.";
-    };
+    package = mkPackageOption pkgs "portunus" { };
 
     seedPath = mkOption {
       type = types.nullOr types.path;
@@ -107,7 +102,9 @@ in
     ldap = {
       package = mkOption {
         type = types.package;
-        # needs openldap built with a libxcrypt that support crypt sha256 until https://github.com/majewsky/portunus/issues/2 is solved
+        # needs openldap built with a libxcrypt that support crypt sha256 until users have had time to migrate to newer hashes
+        # Ref: <https://github.com/majewsky/portunus/issues/2>
+        # TODO: remove in NixOS 24.11 (cf. same note on pkgs/servers/portunus/default.nix)
         default = pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; };
         defaultText = lib.literalExpression "pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; }";
         description = lib.mdDoc "The OpenLDAP package to use.";
@@ -233,7 +230,10 @@ in
         description = "Self-contained authentication service";
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
-        serviceConfig.ExecStart = "${cfg.package.out}/bin/portunus-orchestrator";
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/portunus-orchestrator";
+          Restart = "on-failure";
+        };
         environment = {
           PORTUNUS_LDAP_SUFFIX = cfg.ldap.suffix;
           PORTUNUS_SERVER_BINARY = "${cfg.package}/bin/portunus-server";
@@ -252,6 +252,7 @@ in
             acmeDirectory = config.security.acme.certs."${cfg.domain}".directory;
           in
           {
+            PORTUNUS_SERVER_HTTP_SECURE = "true";
             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;
diff --git a/nixos/modules/services/misc/preload.nix b/nixos/modules/services/misc/preload.nix
new file mode 100644
index 0000000000000..d26e2c3d383e8
--- /dev/null
+++ b/nixos/modules/services/misc/preload.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.preload;
+in {
+  meta = { maintainers = pkgs.preload.meta.maintainers; };
+
+  options.services.preload = {
+    enable = mkEnableOption "preload";
+    package = mkPackageOption pkgs "preload" { };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.preload = {
+      description = "Loads data into ram during idle time of CPU.";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        EnvironmentFile = "${cfg.package}/etc/conf.d/preload";
+        ExecStart = "${getExe cfg.package} -l '' --foreground $PRELOAD_OPTS";
+        Type = "simple";
+        # Only preload data during CPU idle time
+        IOSchedulingClass = 3;
+        DynamicUser = true;
+        StateDirectory = "preload";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/prowlarr.nix b/nixos/modules/services/misc/prowlarr.nix
index 836280d3e5fe3..84d365003992c 100644
--- a/nixos/modules/services/misc/prowlarr.nix
+++ b/nixos/modules/services/misc/prowlarr.nix
@@ -11,7 +11,7 @@ in
     services.prowlarr = {
       enable = mkEnableOption (lib.mdDoc "Prowlarr");
 
-      package = mkPackageOptionMD pkgs "prowlarr" { };
+      package = mkPackageOption pkgs "prowlarr" { };
 
       openFirewall = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/misc/pufferpanel.nix b/nixos/modules/services/misc/pufferpanel.nix
index 2022406c8325c..b951d60cc5b97 100644
--- a/nixos/modules/services/misc/pufferpanel.nix
+++ b/nixos/modules/services/misc/pufferpanel.nix
@@ -33,7 +33,7 @@ in
       '';
     };
 
-    package = lib.mkPackageOptionMD pkgs "pufferpanel" { };
+    package = lib.mkPackageOption pkgs "pufferpanel" { };
 
     extraGroups = lib.mkOption {
       type = lib.types.listOf lib.types.str;
diff --git a/nixos/modules/services/misc/radarr.nix b/nixos/modules/services/misc/radarr.nix
index 834b092c0d14c..618341cf614ff 100644
--- a/nixos/modules/services/misc/radarr.nix
+++ b/nixos/modules/services/misc/radarr.nix
@@ -11,13 +11,7 @@ in
     services.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;
-      };
+      package = mkPackageOption pkgs "radarr" { };
 
       dataDir = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/misc/readarr.nix b/nixos/modules/services/misc/readarr.nix
index dd4fef6e598d4..3c84b13485a47 100644
--- a/nixos/modules/services/misc/readarr.nix
+++ b/nixos/modules/services/misc/readarr.nix
@@ -16,12 +16,7 @@ in
         description = lib.mdDoc "The directory where Readarr stores its data files.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.readarr;
-        defaultText = literalExpression "pkgs.readarr";
-        description = lib.mdDoc "The Readarr package to use";
-      };
+      package = mkPackageOption pkgs "readarr" { };
 
       openFirewall = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index a296fd3816bbb..c1209e34a92b5 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -1,7 +1,8 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkBefore mkDefault mkEnableOption mkIf mkOption mkRemovedOptionModule types;
+  inherit (lib) mkBefore mkDefault mkEnableOption mkPackageOption
+                mkIf mkOption mkRemovedOptionModule types;
   inherit (lib) concatStringsSep literalExpression mapAttrsToList;
   inherit (lib) optional optionalAttrs optionalString;
 
@@ -51,12 +52,8 @@ in
     services.redmine = {
       enable = mkEnableOption (lib.mdDoc "Redmine");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.redmine;
-        defaultText = literalExpression "pkgs.redmine";
-        description = lib.mdDoc "Which Redmine package to use.";
-        example = literalExpression "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }";
+      package = mkPackageOption pkgs "redmine" {
+        example = "redmine.override { ruby = pkgs.ruby_3_2; }";
       };
 
       user = mkOption {
@@ -270,6 +267,9 @@ in
       { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
         message = "services.redmine.database.user must be set to ${cfg.user} if services.redmine.database.createLocally is set true";
       }
+      { assertion = pgsqlLocal -> cfg.database.user == cfg.database.name;
+        message = "services.redmine.database.user and services.redmine.database.name must be the same when using a local postgresql database";
+      }
       { assertion = cfg.database.createLocally -> cfg.database.socket != null;
         message = "services.redmine.database.socket must be set if services.redmine.database.createLocally is set to true";
       }
@@ -315,7 +315,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/misc/rippled.nix b/nixos/modules/services/misc/rippled.nix
index d14b6421b7424..68a8318942506 100644
--- a/nixos/modules/services/misc/rippled.nix
+++ b/nixos/modules/services/misc/rippled.nix
@@ -209,12 +209,7 @@ in
     services.rippled = {
       enable = mkEnableOption (lib.mdDoc "rippled");
 
-      package = mkOption {
-        description = lib.mdDoc "Which rippled package to use.";
-        type = types.package;
-        default = pkgs.rippled;
-        defaultText = literalExpression "pkgs.rippled";
-      };
+      package = mkPackageOption pkgs "rippled" { };
 
       ports = mkOption {
         description = lib.mdDoc "Ports exposed by rippled";
diff --git a/nixos/modules/services/misc/rkvm.nix b/nixos/modules/services/misc/rkvm.nix
new file mode 100644
index 0000000000000..582e8511ed96e
--- /dev/null
+++ b/nixos/modules/services/misc/rkvm.nix
@@ -0,0 +1,164 @@
+{ options, config, pkgs, lib, ... }:
+
+with lib;
+let
+  opt = options.services.rkvm;
+  cfg = config.services.rkvm;
+  toml = pkgs.formats.toml { };
+in
+{
+  meta.maintainers = with maintainers; [ ckie ];
+
+  options.services.rkvm = {
+    enable = mkOption {
+      default = cfg.server.enable || cfg.client.enable;
+      defaultText = literalExpression "config.${opt.server.enable} || config.${opt.client.enable}";
+      type = types.bool;
+      description = mdDoc ''
+        Whether to enable rkvm, a Virtual KVM switch for Linux machines.
+      '';
+    };
+
+    package = mkPackageOption pkgs "rkvm" { };
+
+    server = {
+      enable = mkEnableOption "the rkvm server daemon (input transmitter)";
+
+      settings = mkOption {
+        type = types.submodule
+          {
+            freeformType = toml.type;
+            options = {
+              listen = mkOption {
+                type = types.str;
+                default = "0.0.0.0:5258";
+                description = mdDoc ''
+                  An internet socket address to listen on, either IPv4 or IPv6.
+                '';
+              };
+
+              switch-keys = mkOption {
+                type = types.listOf types.str;
+                default = [ "left-alt" "left-ctrl" ];
+                description = mdDoc ''
+                  A key list specifying a host switch combination.
+
+                  _A list of key names is available in <https://github.com/htrefil/rkvm/blob/master/switch-keys.md>._
+                '';
+              };
+
+              certificate = mkOption {
+                type = types.path;
+                default = "/etc/rkvm/certificate.pem";
+                description = mdDoc ''
+                  TLS certificate path.
+
+                  ::: {.note}
+                  This should be generated with {command}`rkvm-certificate-gen`.
+                  :::
+                '';
+              };
+
+              key = mkOption {
+                type = types.path;
+                default = "/etc/rkvm/key.pem";
+                description = mdDoc ''
+                  TLS key path.
+
+                  ::: {.note}
+                  This should be generated with {command}`rkvm-certificate-gen`.
+                  :::
+                '';
+              };
+
+              password = mkOption {
+                type = types.str;
+                description = mdDoc ''
+                  Shared secret token to authenticate the client.
+                  Make sure this matches your client's config.
+                '';
+              };
+            };
+          };
+
+        default = { };
+        description = mdDoc "Structured server daemon configuration";
+      };
+    };
+
+    client = {
+      enable = mkEnableOption "the rkvm client daemon (input receiver)";
+
+      settings = mkOption {
+        type = types.submodule
+          {
+            freeformType = toml.type;
+            options = {
+              server = mkOption {
+                type = types.str;
+                example = "192.168.0.123:5258";
+                description = mdDoc ''
+                  An RKVM server's internet socket address, either IPv4 or IPv6.
+                '';
+              };
+
+              certificate = mkOption {
+                type = types.path;
+                default = "/etc/rkvm/certificate.pem";
+                description = mdDoc ''
+                  TLS ceritficate path.
+
+                  ::: {.note}
+                  This should be generated with {command}`rkvm-certificate-gen`.
+                  :::
+                '';
+              };
+
+              password = mkOption {
+                type = types.str;
+                description = mdDoc ''
+                  Shared secret token to authenticate the client.
+                  Make sure this matches your server's config.
+                '';
+              };
+            };
+          };
+
+        default = {};
+        description = mdDoc "Structured client daemon configuration";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services =
+      let
+        mkBase = component: {
+          description = "RKVM ${component}";
+          wantedBy = [ "multi-user.target" ];
+          after = {
+            server = [ "network.target" ];
+            client = [ "network-online.target" ];
+          }.${component};
+          wants = {
+            server = [ ];
+            client = [ "network-online.target" ];
+          }.${component};
+          serviceConfig = {
+            ExecStart = "${cfg.package}/bin/rkvm-${component} ${toml.generate "rkvm-${component}.toml" cfg.${component}.settings}";
+            Restart = "always";
+            RestartSec = 5;
+            Type = "simple";
+          };
+        };
+      in
+      {
+        rkvm-server = mkIf cfg.server.enable (mkBase "server");
+        rkvm-client = mkIf cfg.client.enable (mkBase "client");
+      };
+  };
+
+}
diff --git a/nixos/modules/services/misc/rmfakecloud.nix b/nixos/modules/services/misc/rmfakecloud.nix
index 1cdfdeceabcde..979f4f14d3833 100644
--- a/nixos/modules/services/misc/rmfakecloud.nix
+++ b/nixos/modules/services/misc/rmfakecloud.nix
@@ -11,14 +11,11 @@ in {
     services.rmfakecloud = {
       enable = mkEnableOption (lib.mdDoc "rmfakecloud remarkable self-hosted cloud");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.rmfakecloud;
-        defaultText = literalExpression "pkgs.rmfakecloud";
-        description = lib.mdDoc ''
-          rmfakecloud package to use.
-
+      package = mkPackageOption pkgs "rmfakecloud" {
+        extraDescription = ''
+          ::: {.note}
           The default does not include the web user interface.
+          :::
         '';
       };
 
diff --git a/nixos/modules/services/misc/rshim.nix b/nixos/modules/services/misc/rshim.nix
index 0fef2cc228c91..ae13f7d208f6c 100644
--- a/nixos/modules/services/misc/rshim.nix
+++ b/nixos/modules/services/misc/rshim.nix
@@ -12,9 +12,9 @@ let
 in
 {
   options.services.rshim = {
-    enable = lib.mkEnableOption (lib.mdDoc "User-space rshim driver for the BlueField SoC");
+    enable = lib.mkEnableOption (lib.mdDoc "user-space rshim driver for the BlueField SoC");
 
-    package = lib.mkPackageOptionMD pkgs "rshim-user-space" { };
+    package = lib.mkPackageOption pkgs "rshim-user-space" { };
 
     backend = lib.mkOption {
       type = with lib.types; nullOr (enum [ "usb" "pcie" "pcie_lf" ]);
diff --git a/nixos/modules/services/misc/sickbeard.nix b/nixos/modules/services/misc/sickbeard.nix
index bd8d8d8fa7cc4..f141660ced86c 100644
--- a/nixos/modules/services/misc/sickbeard.nix
+++ b/nixos/modules/services/misc/sickbeard.nix
@@ -22,12 +22,9 @@ in
         default = false;
         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 =lib.mdDoc ''
+      package = mkPackageOption pkgs "sickbeard" {
+        example = "sickrage";
+        extraDescription = ''
           Enable `pkgs.sickrage` or `pkgs.sickgear`
           as an alternative to SickBeard
         '';
diff --git a/nixos/modules/services/misc/soft-serve.nix b/nixos/modules/services/misc/soft-serve.nix
new file mode 100644
index 0000000000000..2b63b6bcd8673
--- /dev/null
+++ b/nixos/modules/services/misc/soft-serve.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.soft-serve;
+  configFile = format.generate "config.yaml" cfg.settings;
+  format = pkgs.formats.yaml { };
+  docUrl = "https://charm.sh/blog/self-hosted-soft-serve/";
+  stateDir = "/var/lib/soft-serve";
+in
+{
+  options = {
+    services.soft-serve = {
+      enable = mkEnableOption "soft-serve";
+
+      package = mkPackageOption pkgs "soft-serve" { };
+
+      settings = mkOption {
+        type = format.type;
+        default = { };
+        description = mdDoc ''
+          The contents of the configuration file for soft-serve.
+
+          See <${docUrl}>.
+        '';
+        example = literalExpression ''
+          {
+            name = "dadada's repos";
+            log_format = "text";
+            ssh = {
+              listen_addr = ":23231";
+              public_url = "ssh://localhost:23231";
+              max_timeout = 30;
+              idle_timeout = 120;
+            };
+            stats.listen_addr = ":23233";
+          }
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      # The config file has to be inside the state dir
+      "L+ ${stateDir}/config.yaml - - - - ${configFile}"
+    ];
+
+    systemd.services.soft-serve = {
+      description = "Soft Serve git server";
+      documentation = [ docUrl ];
+      requires = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment.SOFT_SERVE_DATA_PATH = stateDir;
+
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        Restart = "always";
+        ExecStart = "${getExe cfg.package} serve";
+        StateDirectory = "soft-serve";
+        WorkingDirectory = stateDir;
+        RuntimeDirectory = "soft-serve";
+        RuntimeDirectoryMode = "0750";
+        ProcSubset = "pid";
+        ProtectProc = "invisible";
+        UMask = "0027";
+        CapabilityBoundingSet = "";
+        ProtectHome = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RemoveIPC = true;
+        PrivateMounts = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation @debug @keyring @module @mount @obsolete @privileged @raw-io @reboot @setuid @swap"
+        ];
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.dadada ];
+}
diff --git a/nixos/modules/services/misc/sonarr.nix b/nixos/modules/services/misc/sonarr.nix
index 65c51d9677d9e..ec59988d2b9a3 100644
--- a/nixos/modules/services/misc/sonarr.nix
+++ b/nixos/modules/services/misc/sonarr.nix
@@ -36,14 +36,7 @@ in
         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.
-        '';
-      };
+      package = mkPackageOption pkgs "sonarr" { };
     };
   };
 
diff --git a/nixos/modules/services/misc/sourcehut/default.nix b/nixos/modules/services/misc/sourcehut/default.nix
index 580a009a0ad39..aa803d3bb6939 100644
--- a/nixos/modules/services/misc/sourcehut/default.nix
+++ b/nixos/modules/services/misc/sourcehut/default.nix
@@ -1,6 +1,15 @@
 { config, pkgs, lib, ... }:
-with lib;
+
 let
+  inherit (builtins) head tail;
+  inherit (lib) generators maintainers types;
+  inherit (lib.attrsets) attrValues filterAttrs mapAttrs mapAttrsToList recursiveUpdate;
+  inherit (lib.lists) flatten optional optionals;
+  inherit (lib.options) literalExpression mkEnableOption mkOption mkPackageOption;
+  inherit (lib.strings) concatMapStringsSep concatStringsSep optionalString versionOlder;
+  inherit (lib.trivial) mapNullable;
+  inherit (lib.modules) mkBefore mkDefault mkForce mkIf mkMerge
+    mkRemovedOptionModule mkRenamedOptionModule;
   inherit (config.services) nginx postfix postgresql redis;
   inherit (config.users) users groups;
   cfg = config.services.sourcehut;
@@ -25,7 +34,7 @@ let
       || head srvMatch == srv # Include sections for the service being configured
       then v
       # Enable Web links and integrations between services.
-      else if tail srvMatch == [ null ] && elem (head srvMatch) cfg.services
+      else if tail srvMatch == [ null ] && cfg.${head srvMatch}.enable
       then {
         inherit (v) origin;
         # mansrht crashes without it
@@ -38,9 +47,9 @@ let
       # for services needing access to them.
       "builds.sr.ht::worker".buildlogs = "/var/log/sourcehut/buildsrht-worker";
       "git.sr.ht".post-update-script = "/usr/bin/gitsrht-update-hook";
-      "git.sr.ht".repos = "/var/lib/sourcehut/gitsrht/repos";
+      "git.sr.ht".repos = cfg.settings."git.sr.ht".repos;
       "hg.sr.ht".changegroup-script = "/usr/bin/hgsrht-hook-changegroup";
-      "hg.sr.ht".repos = "/var/lib/sourcehut/hgsrht/repos";
+      "hg.sr.ht".repos = cfg.settings."hg.sr.ht".repos;
       # Making this a per service option despite being in a global section,
       # so that it uses the redis-server used by the service.
       "sr.ht".redis-host = cfg.${srv}.redis.host;
@@ -77,6 +86,14 @@ let
       type = types.path;
       apply = s: "<" + toString s;
     };
+    api-origin = mkOption {
+      description = lib.mdDoc "Origin URL for the API";
+      type = types.str;
+      default = "http://${cfg.listenAddress}:${toString (cfg.${srv}.port + 100)}";
+      defaultText = lib.literalMD ''
+        `"http://''${`[](#opt-services.sourcehut.listenAddress)`}:''${toString (`[](#opt-services.sourcehut.${srv}.port)` + 100)}"`
+      '';
+    };
   };
 
   # Specialized python containing all the modules
@@ -112,15 +129,6 @@ in
       and account management services
     '');
 
-    services = mkOption {
-      type = with types; listOf (enum
-        [ "builds" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
-      defaultText = "locally enabled services";
-      description = lib.mdDoc ''
-        Services that may be displayed as links in the title bar of the Web interface.
-      '';
-    };
-
     listenAddress = mkOption {
       type = types.str;
       default = "localhost";
@@ -400,8 +408,8 @@ in
               This setting is propagated to newer and existing repositories.
             '';
             type = types.str;
-            default = "${cfg.python}/bin/hgsrht-hook-changegroup";
-            defaultText = "\${cfg.python}/bin/hgsrht-hook-changegroup";
+            default = "${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup";
+            defaultText = "\${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup";
           };
           repos = mkOption {
             description = lib.mdDoc ''
@@ -438,7 +446,7 @@ in
         };
 
         options."lists.sr.ht" = commonServiceSettings "lists" // {
-          allow-new-lists = mkEnableOption (lib.mdDoc "Allow creation of new lists");
+          allow-new-lists = mkEnableOption (lib.mdDoc "creation of new lists");
           notify-from = mkOption {
             description = lib.mdDoc "Outgoing email for notifications generated by users.";
             type = types.str;
@@ -501,12 +509,6 @@ in
         options."meta.sr.ht" =
           removeAttrs (commonServiceSettings "meta")
             ["oauth-client-id" "oauth-client-secret"] // {
-          api-origin = mkOption {
-            description = lib.mdDoc "Origin URL for API, 100 more than web.";
-            type = types.str;
-            default = "http://${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
-            defaultText = lib.literalMD ''`"http://''${`[](#opt-services.sourcehut.listenAddress)`}:''${toString (`[](#opt-services.sourcehut.meta.port)` + 100)}"`'';
-          };
           webhooks = mkOption {
             description = lib.mdDoc "The Redis connection used for the webhooks worker.";
             type = types.str;
@@ -678,14 +680,8 @@ in
     };
 
     git = {
-      package = mkOption {
-        type = types.package;
-        default = pkgs.git;
-        defaultText = literalExpression "pkgs.git";
-        example = literalExpression "pkgs.gitFull";
-        description = lib.mdDoc ''
-          Git package for git.sr.ht. This can help silence collisions.
-        '';
+      package = mkPackageOption pkgs "git" {
+        example = "gitFull";
       };
       fcgiwrap.preforkProcess = mkOption {
         description = lib.mdDoc "Number of fcgiwrap processes to prefork.";
@@ -695,14 +691,7 @@ in
     };
 
     hg = {
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mercurial;
-        defaultText = literalExpression "pkgs.mercurial";
-        description = lib.mdDoc ''
-          Mercurial package for hg.sr.ht. This can help silence collisions.
-        '';
-      };
+      package = mkPackageOption pkgs "mercurial" { };
       cloneBundles = mkOption {
         type = types.bool;
         default = false;
@@ -784,6 +773,7 @@ in
         extraConfig = ''
           PermitUserEnvironment SRHT_*
         '';
+        startWhenNeeded = false;
       };
       environment.etc."ssh/sourcehut/config.ini".source =
         settingsFormat.generate "sourcehut-dispatch-config.ini"
@@ -792,15 +782,28 @@ in
       environment.etc."ssh/sourcehut/subdir/srht-dispatch" = {
         # sshd_config(5): The program must be owned by root, not writable by group or others
         mode = "0755";
-        source = pkgs.writeShellScript "srht-dispatch" ''
+        source = pkgs.writeShellScript "srht-dispatch-wrapper" ''
           set -e
+          set -x
           cd /etc/ssh/sourcehut/subdir
-          ${cfg.python}/bin/gitsrht-dispatch "$@"
+          ${pkgs.sourcehut.gitsrht}/bin/gitsrht-dispatch "$@"
         '';
       };
+      systemd.tmpfiles.settings."10-sourcehut-gitsrht" = mkIf cfg.git.enable (
+        builtins.listToAttrs (map (name: {
+          name = "/var/log/sourcehut/gitsrht-${name}";
+          value.f = {
+            inherit (cfg.git) user group;
+            mode = "0644";
+          };
+        }) [ "keys" "shell" "update-hook" ])
+      );
       systemd.services.sshd = {
-        #path = optional cfg.git.enable [ cfg.git.package ];
+        preStart = mkIf cfg.hg.enable ''
+          chown ${cfg.hg.user}:${cfg.hg.group} /var/log/sourcehut/hgsrht-keys
+        '';
         serviceConfig = {
+          LogsDirectory = "sourcehut";
           BindReadOnlyPaths =
             # Note that those /usr/bin/* paths are hardcoded in multiple places in *.sr.ht,
             # for instance to get the user from the [git.sr.ht::dispatch] settings.
@@ -813,7 +816,6 @@ in
               "${pkgs.writeShellScript "buildsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/buildsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.buildsrht}/bin/buildsrht-keys "$@"
               ''}:/usr/bin/buildsrht-keys"
               "${pkgs.sourcehut.buildsrht}/bin/master-shell:/usr/bin/master-shell"
@@ -825,31 +827,26 @@ in
               "${pkgs.writeShellScript "gitsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/gitsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-keys "$@"
               ''}:/usr/bin/gitsrht-keys"
               "${pkgs.writeShellScript "gitsrht-shell-wrapper" ''
                 set -e
                 cd /run/sourcehut/gitsrht/subdir
-                set -x
+                export PATH="${cfg.git.package}/bin:$PATH"
+                export SRHT_CONFIG=/run/sourcehut/gitsrht/config.ini
                 exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-shell "$@"
               ''}:/usr/bin/gitsrht-shell"
               "${pkgs.writeShellScript "gitsrht-update-hook" ''
                 set -e
-                test -e "''${PWD%/*}"/config.ini ||
-                # Git hooks are run relative to their repository's directory,
-                # but gitsrht-update-hook looks up ../config.ini
-                ln -s /run/sourcehut/gitsrht/config.ini "''${PWD%/*}"/config.ini
+                export SRHT_CONFIG=/run/sourcehut/gitsrht/config.ini
                 # hooks/post-update calls /usr/bin/gitsrht-update-hook as hooks/stage-3
                 # but this wrapper being a bash script, it overrides $0 with /usr/bin/gitsrht-update-hook
                 # hence this hack to put hooks/stage-3 back into gitsrht-update-hook's $0
                 if test "''${STAGE3:+set}"
                 then
-                  set -x
                   exec -a hooks/stage-3 ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
                 else
                   export STAGE3=set
-                  set -x
                   exec -a "$0" ${pkgs.sourcehut.gitsrht}/bin/gitsrht-update-hook "$@"
                 fi
               ''}:/usr/bin/gitsrht-update-hook"
@@ -860,13 +857,11 @@ in
               "${pkgs.writeShellScript "hgsrht-keys-wrapper" ''
                 set -e
                 cd /run/sourcehut/hgsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-keys "$@"
               ''}:/usr/bin/hgsrht-keys"
               "${pkgs.writeShellScript "hgsrht-shell-wrapper" ''
                 set -e
                 cd /run/sourcehut/hgsrht/subdir
-                set -x
                 exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-shell "$@"
               ''}:/usr/bin/hgsrht-shell"
               # Mercurial's changegroup hooks are run relative to their repository's directory,
@@ -875,8 +870,7 @@ in
                 set -e
                 test -e "''$PWD"/config.ini ||
                 ln -s /run/sourcehut/hgsrht/config.ini "''$PWD"/config.ini
-                set -x
-                exec -a "$0" ${cfg.python}/bin/hgsrht-hook-changegroup "$@"
+                exec -a "$0" ${pkgs.sourcehut.hgsrht}/bin/hgsrht-hook-changegroup "$@"
               ''}:/usr/bin/hgsrht-hook-changegroup"
             ];
         };
@@ -1066,10 +1060,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-api.serviceConfig = {
+        Restart = "always";
+        RestartSec = "5s";
+        ExecStart = "${pkgs.sourcehut.gitsrht}/bin/gitsrht-api -b ${cfg.listenAddress}:${toString (cfg.git.port + 100)}";
+        BindPaths = [ "${cfg.settings."git.sr.ht".repos}:/var/lib/sourcehut/gitsrht/repos" ];
       };
       extraServices.gitsrht-fcgiwrap = mkIf cfg.nginx.enable {
         serviceConfig = {
@@ -1188,7 +1183,7 @@ in
       extraServices.listssrht-lmtp = {
         wants = [ "postfix.service" ];
         unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
-        serviceConfig.ExecStart = "${cfg.python}/bin/listssrht-lmtp";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.listssrht}/bin/listssrht-lmtp";
         # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
         serviceConfig.PrivateUsers = mkForce false;
       };
@@ -1252,55 +1247,30 @@ in
           ) cfg.settings));
         serviceConfig.ExecStart = "${pkgs.sourcehut.metasrht}/bin/metasrht-api -b ${cfg.listenAddress}:${toString (cfg.meta.port + 100)}";
       };
-      extraConfig = mkMerge [
-        {
-          assertions = [
-            { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
-                          s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
-              message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
-            }
-          ];
-          environment.systemPackages = optional cfg.meta.enable
-            (pkgs.writeShellScriptBin "metasrht-manageuser" ''
-              set -eux
-              if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
-              then exec sudo -u '${cfg.meta.user}' "$0" "$@"
-              else
-                # In order to load config.ini
-                if cd /run/sourcehut/metasrht
-                then exec ${cfg.python}/bin/metasrht-manageuser "$@"
-                else cat <<EOF
-                  Please run: sudo systemctl start metasrht
-              EOF
-                  exit 1
-                fi
+      extraConfig = {
+        assertions = [
+          { assertion = let s = cfg.settings."meta.sr.ht::billing"; in
+                        s.enabled == "yes" -> (s.stripe-public-key != null && s.stripe-secret-key != null);
+            message = "If meta.sr.ht::billing is enabled, the keys must be defined.";
+          }
+        ];
+        environment.systemPackages = optional cfg.meta.enable
+          (pkgs.writeShellScriptBin "metasrht-manageuser" ''
+            set -eux
+            if test "$(${pkgs.coreutils}/bin/id -n -u)" != '${cfg.meta.user}'
+            then exec sudo -u '${cfg.meta.user}' "$0" "$@"
+            else
+              # In order to load config.ini
+              if cd /run/sourcehut/metasrht
+              then exec ${pkgs.sourcehut.metasrht}/bin/metasrht-manageuser "$@"
+              else cat <<EOF
+                Please run: sudo systemctl start metasrht
+            EOF
+                exit 1
               fi
-            '');
-        }
-        (mkIf cfg.nginx.enable {
-          services.nginx.virtualHosts."meta.${domain}" = {
-            locations."/query" = {
-              proxyPass = cfg.settings."meta.sr.ht".api-origin;
-              extraConfig = ''
-                if ($request_method = 'OPTIONS') {
-                  add_header 'Access-Control-Allow-Origin' '*';
-                  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-                  add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-                  add_header 'Access-Control-Max-Age' 1728000;
-                  add_header 'Content-Type' 'text/plain; charset=utf-8';
-                  add_header 'Content-Length' 0;
-                  return 204;
-                }
-
-                add_header 'Access-Control-Allow-Origin' '*';
-                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
-                add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
-                add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
-              '';
-            };
-          };
-        })
-      ];
+            fi
+          '');
+      };
     })
 
     (import ./service.nix "pages" {
@@ -1342,6 +1312,11 @@ in
     (import ./service.nix "paste" {
       inherit configIniOfService;
       port = 5011;
+      extraServices.pastesrht-api = {
+        serviceConfig.Restart = "always";
+        serviceConfig.RestartSec = "5s";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.pastesrht}/bin/pastesrht-api -b ${cfg.listenAddress}:${toString (cfg.paste.port + 100)}";
+      };
     })
 
     (import ./service.nix "todo" {
@@ -1356,7 +1331,7 @@ in
       extraServices.todosrht-lmtp = {
         wants = [ "postfix.service" ];
         unitConfig.JoinsNamespaceOf = optional cfg.postfix.enable "postfix.service";
-        serviceConfig.ExecStart = "${cfg.python}/bin/todosrht-lmtp";
+        serviceConfig.ExecStart = "${pkgs.sourcehut.todosrht}/bin/todosrht-lmtp";
         # Avoid crashing: os.chown(sock, os.getuid(), sock_gid)
         serviceConfig.PrivateUsers = mkForce false;
       };
@@ -1388,8 +1363,12 @@ in
         dispatch is deprecated. See https://sourcehut.org/blog/2022-08-01-dispatch-deprecation-plans/
         for more information.
     '')
+
+    (mkRemovedOptionModule [ "services" "sourcehut" "services"] ''
+        This option was removed in favor of individual <service>.enable flags.
+    '')
   ];
 
   meta.doc = ./default.md;
-  meta.maintainers = with maintainers; [ tomberek ];
+  meta.maintainers = with maintainers; [ tomberek nessdoor ];
 }
diff --git a/nixos/modules/services/misc/sourcehut/service.nix b/nixos/modules/services/misc/sourcehut/service.nix
index aae13e0cc2c92..4a8289b4d4032 100644
--- a/nixos/modules/services/misc/sourcehut/service.nix
+++ b/nixos/modules/services/misc/sourcehut/service.nix
@@ -3,117 +3,133 @@ srv:
 , srvsrht ? "${srv}srht" # Because "buildsrht" does not follow that pattern (missing an "s").
 , iniKey ? "${srv}.sr.ht"
 , webhooks ? false
-, extraTimers ? {}
-, mainService ? {}
-, extraServices ? {}
-, extraConfig ? {}
+, extraTimers ? { }
+, mainService ? { }
+, extraServices ? { }
+, extraConfig ? { }
 , port
 }:
 { config, lib, pkgs, ... }:
 
-with lib;
 let
+  inherit (lib) types;
+  inherit (lib.attrsets) mapAttrs optionalAttrs;
+  inherit (lib.lists) optional;
+  inherit (lib.modules) mkBefore mkDefault mkForce mkIf mkMerge;
+  inherit (lib.options) mkEnableOption mkOption;
+  inherit (lib.strings) concatStringsSep hasSuffix optionalString;
   inherit (config.services) postgresql;
   redis = config.services.redis.servers."sourcehut-${srvsrht}";
   inherit (config.users) users;
   cfg = config.services.sourcehut;
   configIni = configIniOfService srv;
   srvCfg = cfg.${srv};
-  baseService = serviceName: { allowStripe ? false }: extraService: let
-    runDir = "/run/sourcehut/${serviceName}";
-    rootDir = "/run/sourcehut/chroots/${serviceName}";
+  baseService = serviceName: { allowStripe ? false }: extraService:
+    let
+      runDir = "/run/sourcehut/${serviceName}";
+      rootDir = "/run/sourcehut/chroots/${serviceName}";
     in
-    mkMerge [ extraService {
-    after = [ "network.target" ] ++
-      optional cfg.postgresql.enable "postgresql.service" ++
-      optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
-    requires =
-      optional cfg.postgresql.enable "postgresql.service" ++
-      optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
-    path = [ pkgs.gawk ];
-    environment.HOME = runDir;
-    serviceConfig = {
-      User = mkDefault srvCfg.user;
-      Group = mkDefault srvCfg.group;
-      RuntimeDirectory = [
-        "sourcehut/${serviceName}"
-        # Used by *srht-keys which reads ../config.ini
-        "sourcehut/${serviceName}/subdir"
-        "sourcehut/chroots/${serviceName}"
-      ];
-      RuntimeDirectoryMode = "2750";
-      # No need for the chroot path once inside the chroot
-      InaccessiblePaths = [ "-+${rootDir}" ];
-      # g+rx is for group members (eg. fcgiwrap or nginx)
-      # to read Git/Mercurial repositories, buildlogs, etc.
-      # o+x is for intermediate directories created by BindPaths= and like,
-      # as they're owned by root:root.
-      UMask = "0026";
-      RootDirectory = rootDir;
-      RootDirectoryStartOnly = true;
-      PrivateTmp = true;
-      MountAPIVFS = true;
-      # config.ini is looked up in there, before /etc/srht/config.ini
-      # Note that it fails to be set in ExecStartPre=
-      WorkingDirectory = mkDefault ("-"+runDir);
-      BindReadOnlyPaths = [
-        builtins.storeDir
-        "/etc"
-        "/run/booted-system"
-        "/run/current-system"
-        "/run/systemd"
-        ] ++
-        optional cfg.postgresql.enable "/run/postgresql" ++
-        optional cfg.redis.enable "/run/redis-sourcehut-${srvsrht}";
-      # LoadCredential= are unfortunately not available in ExecStartPre=
-      # Hence this one is run as root (the +) with RootDirectoryStartOnly=
-      # to reach credentials wherever they are.
-      # Note that each systemd service gets its own ${runDir}/config.ini file.
-      ExecStartPre = mkBefore [("+"+pkgs.writeShellScript "${serviceName}-credentials" ''
-        set -x
-        # Replace values 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
-      '')];
-      # The following options are only for optimizing:
-      # systemd-analyze security
-      AmbientCapabilities = "";
-      CapabilityBoundingSet = "";
-      # ProtectClock= adds DeviceAllow=char-rtc r
-      DeviceAllow = "";
-      LockPersonality = true;
-      MemoryDenyWriteExecute = true;
-      NoNewPrivileges = true;
-      PrivateDevices = true;
-      PrivateMounts = true;
-      PrivateNetwork = mkDefault false;
-      PrivateUsers = true;
-      ProcSubset = "pid";
-      ProtectClock = true;
-      ProtectControlGroups = true;
-      ProtectHome = true;
-      ProtectHostname = true;
-      ProtectKernelLogs = true;
-      ProtectKernelModules = true;
-      ProtectKernelTunables = true;
-      ProtectProc = "invisible";
-      ProtectSystem = "strict";
-      RemoveIPC = true;
-      RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
-      RestrictNamespaces = true;
-      RestrictRealtime = true;
-      RestrictSUIDSGID = true;
-      #SocketBindAllow = [ "tcp:${toString srvCfg.port}" "tcp:${toString srvCfg.prometheusPort}" ];
-      #SocketBindDeny = "any";
-      SystemCallFilter = [
-        "@system-service"
-        "~@aio" "~@keyring" "~@memlock" "~@privileged" "~@resources" "~@timer"
-        "@chown" "@setuid"
-      ];
-      SystemCallArchitectures = "native";
-    };
-  } ];
+    mkMerge [
+      extraService
+      {
+        after = [ "network.target" ] ++
+          optional cfg.postgresql.enable "postgresql.service" ++
+          optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
+        requires =
+          optional cfg.postgresql.enable "postgresql.service" ++
+          optional cfg.redis.enable "redis-sourcehut-${srvsrht}.service";
+        path = [ pkgs.gawk ];
+        environment.HOME = runDir;
+        serviceConfig = {
+          User = mkDefault srvCfg.user;
+          Group = mkDefault srvCfg.group;
+          RuntimeDirectory = [
+            "sourcehut/${serviceName}"
+            # Used by *srht-keys which reads ../config.ini
+            "sourcehut/${serviceName}/subdir"
+            "sourcehut/chroots/${serviceName}"
+          ];
+          RuntimeDirectoryMode = "2750";
+          # No need for the chroot path once inside the chroot
+          InaccessiblePaths = [ "-+${rootDir}" ];
+          # g+rx is for group members (eg. fcgiwrap or nginx)
+          # to read Git/Mercurial repositories, buildlogs, etc.
+          # o+x is for intermediate directories created by BindPaths= and like,
+          # as they're owned by root:root.
+          UMask = "0026";
+          RootDirectory = rootDir;
+          RootDirectoryStartOnly = true;
+          PrivateTmp = true;
+          MountAPIVFS = true;
+          # config.ini is looked up in there, before /etc/srht/config.ini
+          # Note that it fails to be set in ExecStartPre=
+          WorkingDirectory = mkDefault ("-" + runDir);
+          BindReadOnlyPaths = [
+            builtins.storeDir
+            "/etc"
+            "/run/booted-system"
+            "/run/current-system"
+            "/run/systemd"
+          ] ++
+          optional cfg.postgresql.enable "/run/postgresql" ++
+          optional cfg.redis.enable "/run/redis-sourcehut-${srvsrht}";
+          # LoadCredential= are unfortunately not available in ExecStartPre=
+          # Hence this one is run as root (the +) with RootDirectoryStartOnly=
+          # to reach credentials wherever they are.
+          # Note that each systemd service gets its own ${runDir}/config.ini file.
+          ExecStartPre = mkBefore [
+            ("+" + pkgs.writeShellScript "${serviceName}-credentials" ''
+              set -x
+              # Replace values 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
+            '')
+          ];
+          # The following options are only for optimizing:
+          # systemd-analyze security
+          AmbientCapabilities = "";
+          CapabilityBoundingSet = "";
+          # ProtectClock= adds DeviceAllow=char-rtc r
+          DeviceAllow = "";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateMounts = true;
+          PrivateNetwork = mkDefault false;
+          PrivateUsers = true;
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          #SocketBindAllow = [ "tcp:${toString srvCfg.port}" "tcp:${toString srvCfg.prometheusPort}" ];
+          #SocketBindDeny = "any";
+          SystemCallFilter = [
+            "@system-service"
+            "~@aio"
+            "~@keyring"
+            "~@memlock"
+            "~@privileged"
+            "~@timer"
+            "@chown"
+            "@setuid"
+          ];
+          SystemCallArchitectures = "native";
+        };
+      }
+    ];
 in
 {
   options.services.sourcehut.${srv} = {
@@ -173,7 +189,7 @@ in
     gunicorn = {
       extraArgs = mkOption {
         type = with types; listOf str;
-        default = ["--timeout 120" "--workers 1" "--log-level=info"];
+        default = [ "--timeout 120" "--workers 1" "--log-level=info" ];
         description = lib.mdDoc "Extra arguments passed to Gunicorn.";
       };
     };
@@ -181,7 +197,7 @@ in
     webhooks = {
       extraArgs = mkOption {
         type = with types; listOf str;
-        default = ["--loglevel DEBUG" "--pool eventlet" "--without-heartbeat"];
+        default = [ "--loglevel DEBUG" "--pool eventlet" "--without-heartbeat" ];
         description = lib.mdDoc "Extra arguments passed to the Celery responsible for webhooks.";
       };
       celeryConfig = mkOption {
@@ -192,184 +208,237 @@ in
     };
   };
 
-  config = lib.mkIf (cfg.enable && srvCfg.enable) (mkMerge [ extraConfig {
-    users = {
+  config = lib.mkIf (cfg.enable && srvCfg.enable) (mkMerge [
+    extraConfig
+    {
       users = {
-        "${srvCfg.user}" = {
-          isSystemUser = true;
-          group = mkDefault srvCfg.group;
-          description = mkDefault "sourcehut user for ${srv}.sr.ht";
+        users = {
+          "${srvCfg.user}" = {
+            isSystemUser = true;
+            group = mkDefault srvCfg.group;
+            description = mkDefault "sourcehut user for ${srv}.sr.ht";
+          };
         };
+        groups = {
+          "${srvCfg.group}" = { };
+        } // optionalAttrs
+          (cfg.postgresql.enable
+            && hasSuffix "0" (postgresql.settings.unix_socket_permissions or ""))
+          {
+            "postgres".members = [ srvCfg.user ];
+          } // optionalAttrs
+          (cfg.redis.enable
+            && hasSuffix "0" (redis.settings.unixsocketperm or ""))
+          {
+            "redis-sourcehut-${srvsrht}".members = [ srvCfg.user ];
+          };
       };
-      groups = {
-        "${srvCfg.group}" = { };
-      } // optionalAttrs (cfg.postgresql.enable
-        && hasSuffix "0" (postgresql.settings.unix_socket_permissions or "")) {
-        "postgres".members = [ srvCfg.user ];
-      } // optionalAttrs (cfg.redis.enable
-        && hasSuffix "0" (redis.settings.unixsocketperm or "")) {
-        "redis-sourcehut-${srvsrht}".members = [ srvCfg.user ];
-      };
-    };
 
-    services.nginx = mkIf cfg.nginx.enable {
-      virtualHosts."${srv}.${cfg.settings."sr.ht".global-domain}" = mkMerge [ {
-        forceSSL = mkDefault true;
-        locations."/".proxyPass = "http://${cfg.listenAddress}:${toString srvCfg.port}";
-        locations."/static" = {
-          root = "${pkgs.sourcehut.${srvsrht}}/${pkgs.sourcehut.python.sitePackages}/${srvsrht}";
-          extraConfig = mkDefault ''
-            expires 30d;
-          '';
-        };
-      } cfg.nginx.virtualHost ];
-    };
+      services.nginx = mkIf cfg.nginx.enable {
+        virtualHosts."${srv}.${cfg.settings."sr.ht".global-domain}" = mkMerge [{
+          forceSSL = mkDefault true;
+          locations."/".proxyPass = "http://${cfg.listenAddress}:${toString srvCfg.port}";
+          locations."/static" = {
+            root = "${pkgs.sourcehut.${srvsrht}}/${pkgs.sourcehut.python.sitePackages}/${srvsrht}";
+            extraConfig = mkDefault ''
+              expires 30d;
+            '';
+          };
+          locations."/query" = mkIf (cfg.settings.${iniKey} ? api-origin) {
+            proxyPass = cfg.settings.${iniKey}.api-origin;
+            extraConfig = ''
+              add_header 'Access-Control-Allow-Origin' '*';
+              add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
+              add_header 'Access-Control-Allow-Headers' 'User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
 
-    services.postgresql = mkIf cfg.postgresql.enable {
-      authentication = ''
-        local ${srvCfg.postgresql.database} ${srvCfg.user} trust
-      '';
-      ensureDatabases = [ srvCfg.postgresql.database ];
-      ensureUsers = map (name: {
-          inherit name;
-          ensurePermissions = { "DATABASE \"${srvCfg.postgresql.database}\"" = "ALL PRIVILEGES"; };
-        }) [srvCfg.user];
-    };
+              if ($request_method = 'OPTIONS') {
+                add_header 'Access-Control-Max-Age' 1728000;
+                add_header 'Content-Type' 'text/plain; charset=utf-8';
+                add_header 'Content-Length' 0;
+                return 204;
+              }
 
-    services.sourcehut.services = mkDefault (filter (s: cfg.${s}.enable)
-      [ "builds" "dispatch" "git" "hg" "hub" "lists" "man" "meta" "pages" "paste" "todo" ]);
+              add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
+            '';
+          };
+        }
+          cfg.nginx.virtualHost];
+      };
 
-    services.sourcehut.settings = mkMerge [
-      {
-        "${srv}.sr.ht".origin = mkDefault "https://${srv}.${cfg.settings."sr.ht".global-domain}";
-      }
+      services.postgresql = mkIf cfg.postgresql.enable {
+        authentication = ''
+          local ${srvCfg.postgresql.database} ${srvCfg.user} trust
+        '';
+        ensureDatabases = [ srvCfg.postgresql.database ];
+        ensureUsers = map
+          (name: {
+            inherit name;
+            # We don't use it because we have a special default database name with dots.
+            # TODO(for maintainers of sourcehut): migrate away from custom preStart script.
+            ensureDBOwnership = false;
+          }) [ srvCfg.user ];
+      };
 
-      (mkIf cfg.postgresql.enable {
-        "${srv}.sr.ht".connection-string = mkDefault "postgresql:///${srvCfg.postgresql.database}?user=${srvCfg.user}&host=/run/postgresql";
-      })
-    ];
 
-    services.redis.servers."sourcehut-${srvsrht}" = mkIf cfg.redis.enable {
-      enable = true;
-      databases = 3;
-      syslog = true;
-      # TODO: set a more informed value
-      save = mkDefault [ [1800 10] [300 100] ];
-      settings = {
+      services.sourcehut.settings = mkMerge [
+        {
+          "${srv}.sr.ht".origin = mkDefault "https://${srv}.${cfg.settings."sr.ht".global-domain}";
+        }
+
+        (mkIf cfg.postgresql.enable {
+          "${srv}.sr.ht".connection-string = mkDefault "postgresql:///${srvCfg.postgresql.database}?user=${srvCfg.user}&host=/run/postgresql";
+        })
+      ];
+
+      services.redis.servers."sourcehut-${srvsrht}" = mkIf cfg.redis.enable {
+        enable = true;
+        databases = 3;
+        syslog = true;
         # TODO: set a more informed value
-        maxmemory = "128MB";
-        maxmemory-policy = "volatile-ttl";
+        save = mkDefault [ [ 1800 10 ] [ 300 100 ] ];
+        settings = {
+          # TODO: set a more informed value
+          maxmemory = "128MB";
+          maxmemory-policy = "volatile-ttl";
+        };
       };
-    };
 
-    systemd.services = mkMerge [
-      {
-        "${srvsrht}" = baseService srvsrht { allowStripe = srv == "meta"; } (mkMerge [
+      systemd.services = mkMerge [
         {
-          description = "sourcehut ${srv}.sr.ht website service";
-          before = optional cfg.nginx.enable "nginx.service";
-          wants = optional cfg.nginx.enable "nginx.service";
-          wantedBy = [ "multi-user.target" ];
-          path = optional cfg.postgresql.enable postgresql.package;
-          # Beware: change in credentials' content will not trigger restart.
-          restartTriggers = [ configIni ];
-          serviceConfig = {
-            Type = "simple";
-            Restart = mkDefault "always";
-            #RestartSec = mkDefault "2min";
-            StateDirectory = [ "sourcehut/${srvsrht}" ];
-            StateDirectoryMode = "2750";
-            ExecStart = "${cfg.python}/bin/gunicorn ${srvsrht}.app:app --name ${srvsrht} --bind ${cfg.listenAddress}:${toString srvCfg.port} " + concatStringsSep " " srvCfg.gunicorn.extraArgs;
-          };
-          preStart = let
-            version = pkgs.sourcehut.${srvsrht}.version;
-            stateDir = "/var/lib/sourcehut/${srvsrht}";
-            in mkBefore ''
-            set -x
-            # Use the /run/sourcehut/${srvsrht}/config.ini
-            # installed by a previous ExecStartPre= in baseService
-            cd /run/sourcehut/${srvsrht}
+          "${srvsrht}" = baseService srvsrht { allowStripe = srv == "meta"; } (mkMerge [
+            {
+              description = "sourcehut ${srv}.sr.ht website service";
+              before = optional cfg.nginx.enable "nginx.service";
+              wants = optional cfg.nginx.enable "nginx.service";
+              wantedBy = [ "multi-user.target" ];
+              path = optional cfg.postgresql.enable postgresql.package;
+              # Beware: change in credentials' content will not trigger restart.
+              restartTriggers = [ configIni ];
+              serviceConfig = {
+                Type = "simple";
+                Restart = mkDefault "always";
+                #RestartSec = mkDefault "2min";
+                StateDirectory = [ "sourcehut/${srvsrht}" ];
+                StateDirectoryMode = "2750";
+                ExecStart = "${cfg.python}/bin/gunicorn ${srvsrht}.app:app --name ${srvsrht} --bind ${cfg.listenAddress}:${toString srvCfg.port} " + concatStringsSep " " srvCfg.gunicorn.extraArgs;
+              };
+              preStart =
+                let
+                  version = pkgs.sourcehut.${srvsrht}.version;
+                  stateDir = "/var/lib/sourcehut/${srvsrht}";
+                in
+                mkBefore ''
+                  set -x
+                  # Use the /run/sourcehut/${srvsrht}/config.ini
+                  # installed by a previous ExecStartPre= in baseService
+                  cd /run/sourcehut/${srvsrht}
 
-            if test ! -e ${stateDir}/db; then
-              # Setup the initial database.
-              # Note that it stamps the alembic head afterward
-              ${cfg.python}/bin/${srvsrht}-initdb
-              echo ${version} >${stateDir}/db
-            fi
+                  if test ! -e ${stateDir}/db; then
+                    # Setup the initial database.
+                    # Note that it stamps the alembic head afterward
+                    ${cfg.python}/bin/${srvsrht}-initdb
+                    echo ${version} >${stateDir}/db
+                  fi
 
-            ${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
-              if [ "$(cat ${stateDir}/db)" != "${version}" ]; then
-                # Manage schema migrations using alembic
-                ${cfg.python}/bin/${srvsrht}-migrate -a upgrade head
-                echo ${version} >${stateDir}/db
-              fi
-            ''}
+                  ${optionalString cfg.settings.${iniKey}.migrate-on-upgrade ''
+                    if [ "$(cat ${stateDir}/db)" != "${version}" ]; then
+                      # Manage schema migrations using alembic
+                      ${cfg.python}/bin/${srvsrht}-migrate -a upgrade head
+                      echo ${version} >${stateDir}/db
+                    fi
+                  ''}
 
-            # Update copy of each users' profile to the latest
-            # See https://lists.sr.ht/~sircmpwn/sr.ht-admins/<20190302181207.GA13778%40cirno.my.domain>
-            if test ! -e ${stateDir}/webhook; then
-              # Update ${iniKey}'s users' profile copy to the latest
-              ${cfg.python}/bin/srht-update-profiles ${iniKey}
-              touch ${stateDir}/webhook
-            fi
-          '';
-        } mainService ]);
-      }
+                  # Update copy of each users' profile to the latest
+                  # See https://lists.sr.ht/~sircmpwn/sr.ht-admins/<20190302181207.GA13778%40cirno.my.domain>
+                  if test ! -e ${stateDir}/webhook; then
+                    # Update ${iniKey}'s users' profile copy to the latest
+                    ${cfg.python}/bin/srht-update-profiles ${iniKey}
+                    touch ${stateDir}/webhook
+                  fi
+                '';
+            }
+            mainService
+          ]);
+        }
 
-      (mkIf webhooks {
-        "${srvsrht}-webhooks" = baseService "${srvsrht}-webhooks" {}
-          {
-            description = "sourcehut ${srv}.sr.ht webhooks service";
-            after = [ "${srvsrht}.service" ];
-            wantedBy = [ "${srvsrht}.service" ];
-            partOf = [ "${srvsrht}.service" ];
-            preStart = ''
-              cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" srvCfg.webhooks.celeryConfig} \
-                 /run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
-            '';
-            serviceConfig = {
-              Type = "simple";
-              Restart = "always";
-              ExecStart = "${cfg.python}/bin/celery --app ${srvsrht}.webhooks worker --hostname ${srvsrht}-webhooks@%%h " + concatStringsSep " " srvCfg.webhooks.extraArgs;
-              # Avoid crashing: os.getloadavg()
-              ProcSubset = mkForce "all";
+        (mkIf webhooks {
+          "${srvsrht}-webhooks" = baseService "${srvsrht}-webhooks" { }
+            {
+              description = "sourcehut ${srv}.sr.ht webhooks service";
+              after = [ "${srvsrht}.service" ];
+              wantedBy = [ "${srvsrht}.service" ];
+              partOf = [ "${srvsrht}.service" ];
+              preStart = ''
+                cp ${pkgs.writeText "${srvsrht}-webhooks-celeryconfig.py" srvCfg.webhooks.celeryConfig} \
+                   /run/sourcehut/${srvsrht}-webhooks/celeryconfig.py
+              '';
+              serviceConfig = {
+                Type = "simple";
+                Restart = "always";
+                ExecStart = "${cfg.python}/bin/celery --app ${srvsrht}.webhooks worker --hostname ${srvsrht}-webhooks@%%h " + concatStringsSep " " srvCfg.webhooks.extraArgs;
+                # Avoid crashing: os.getloadavg()
+                ProcSubset = mkForce "all";
+              };
             };
-          };
-      })
+        })
 
-      (mapAttrs (timerName: timer: (baseService timerName {} (mkMerge [
-        {
-          description = "sourcehut ${timerName} service";
-          after = [ "network.target" "${srvsrht}.service" ];
-          serviceConfig = {
-            Type = "oneshot";
-            ExecStart = "${cfg.python}/bin/${timerName}";
-          };
-        }
-        (timer.service or {})
-      ]))) extraTimers)
+        (mapAttrs
+          (timerName: timer: (baseService timerName { } (mkMerge [
+            {
+              description = "sourcehut ${timerName} service";
+              after = [ "network.target" "${srvsrht}.service" ];
+              serviceConfig = {
+                Type = "oneshot";
+                ExecStart = "${cfg.python}/bin/${timerName}";
+              };
+            }
+            (timer.service or { })
+          ])))
+          extraTimers)
 
-      (mapAttrs (serviceName: extraService: baseService serviceName {} (mkMerge [
-        {
-          description = "sourcehut ${serviceName} service";
-          # So that extraServices have the PostgreSQL database initialized.
-          after = [ "${srvsrht}.service" ];
-          wantedBy = [ "${srvsrht}.service" ];
-          partOf = [ "${srvsrht}.service" ];
-          serviceConfig = {
-            Type = "simple";
-            Restart = mkDefault "always";
-          };
-        }
-        extraService
-      ])) extraServices)
-    ];
+        (mapAttrs
+          (serviceName: extraService: baseService serviceName { } (mkMerge [
+            {
+              description = "sourcehut ${serviceName} service";
+              # So that extraServices have the PostgreSQL database initialized.
+              after = [ "${srvsrht}.service" ];
+              wantedBy = [ "${srvsrht}.service" ];
+              partOf = [ "${srvsrht}.service" ];
+              serviceConfig = {
+                Type = "simple";
+                Restart = mkDefault "always";
+              };
+            }
+            extraService
+          ]))
+          extraServices)
 
-    systemd.timers = mapAttrs (timerName: timer:
-      {
-        description = "sourcehut timer for ${timerName}";
-        wantedBy = [ "timers.target" ];
-        inherit (timer) timerConfig;
-      }) extraTimers;
-  } ]);
+        # Work around 'pq: permission denied for schema public' with postgres v15.
+        # See https://github.com/NixOS/nixpkgs/issues/216989
+        # Workaround taken from nixos/forgejo: https://github.com/NixOS/nixpkgs/pull/262741
+        # TODO(to maintainers of sourcehut): please migrate away from this workaround
+        # by migrating away from database name defaults with dots.
+        (lib.mkIf
+          (
+            cfg.postgresql.enable
+            && lib.strings.versionAtLeast config.services.postgresql.package.version "15.0"
+          )
+          {
+            postgresql.postStart = (lib.mkAfter ''
+              $PSQL -tAc 'ALTER DATABASE "${srvCfg.postgresql.database}" OWNER TO "${srvCfg.user}";'
+            '');
+          }
+        )
+      ];
+
+      systemd.timers = mapAttrs
+        (timerName: timer:
+          {
+            description = "sourcehut timer for ${timerName}";
+            wantedBy = [ "timers.target" ];
+            inherit (timer) timerConfig;
+          })
+        extraTimers;
+    }
+  ]);
 }
diff --git a/nixos/modules/services/misc/spice-autorandr.nix b/nixos/modules/services/misc/spice-autorandr.nix
new file mode 100644
index 0000000000000..0d8830dbd5be2
--- /dev/null
+++ b/nixos/modules/services/misc/spice-autorandr.nix
@@ -0,0 +1,26 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.spice-autorandr;
+in
+{
+  options = {
+    services.spice-autorandr = {
+      enable = lib.mkEnableOption (lib.mdDoc "spice-autorandr service that will automatically resize display to match SPICE client window size.");
+      package = lib.mkPackageOption pkgs "spice-autorandr" { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.user.services.spice-autorandr = {
+      wantedBy = [ "default.target" ];
+      after = [ "spice-vdagentd.service" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/spice-autorandr";
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/spice-webdavd.nix b/nixos/modules/services/misc/spice-webdavd.nix
index 6c817e429ac66..2b4304365618d 100644
--- a/nixos/modules/services/misc/spice-webdavd.nix
+++ b/nixos/modules/services/misc/spice-webdavd.nix
@@ -9,12 +9,7 @@ in
     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.";
-      };
+      package = mkPackageOption pkgs "phodav" { };
     };
   };
 
diff --git a/nixos/modules/services/misc/tandoor-recipes.nix b/nixos/modules/services/misc/tandoor-recipes.nix
index 63d3e3d2a8577..6c51a9bb85550 100644
--- a/nixos/modules/services/misc/tandoor-recipes.nix
+++ b/nixos/modules/services/misc/tandoor-recipes.nix
@@ -12,7 +12,7 @@ let
     DEBUG_TOOLBAR = "0";
     MEDIA_ROOT = "/var/lib/tandoor-recipes";
   } // optionalAttrs (config.time.timeZone != null) {
-    TIMEZONE = config.time.timeZone;
+    TZ = config.time.timeZone;
   } // (
     lib.mapAttrs (_: toString) cfg.extraConfig
   );
@@ -71,12 +71,7 @@ in
       };
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.tandoor-recipes;
-      defaultText = literalExpression "pkgs.tandoor-recipes";
-      description = lib.mdDoc "The Tandoor Recipes package to use.";
-    };
+    package = mkPackageOption pkgs "tandoor-recipes" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/misc/taskserver/helper-tool.py b/nixos/modules/services/misc/taskserver/helper-tool.py
index fec05728b2b6b..b1eebb07686b2 100644
--- a/nixos/modules/services/misc/taskserver/helper-tool.py
+++ b/nixos/modules/services/misc/taskserver/helper-tool.py
@@ -61,6 +61,10 @@ def run_as_taskd_user():
     os.setuid(uid)
 
 
+def run_as_taskd_group():
+    gid = grp.getgrnam(TASKD_GROUP).gr_gid
+    os.setgid(gid)
+
 def taskd_cmd(cmd, *args, **kwargs):
     """
     Invoke taskd with the specified command with the privileges of the 'taskd'
@@ -90,7 +94,7 @@ def certtool_cmd(*args, **kwargs):
     """
     return subprocess.check_output(
         [CERTTOOL_COMMAND] + list(args),
-        preexec_fn=lambda: os.umask(0o077),
+        preexec_fn=run_as_taskd_group,
         stderr=subprocess.STDOUT,
         **kwargs
     )
@@ -156,17 +160,33 @@ def generate_key(org, user):
         sys.stderr.write(msg.format(user))
         return
 
-    basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user)
-    if os.path.exists(basedir):
+    keysdir = os.path.join(TASKD_DATA_DIR, "keys" )
+    orgdir  = os.path.join(keysdir       , org    )
+    userdir = os.path.join(orgdir        , user   )
+    if os.path.exists(userdir):
         raise OSError("Keyfile directory for {} already exists.".format(user))
 
-    privkey = os.path.join(basedir, "private.key")
-    pubcert = os.path.join(basedir, "public.cert")
+    privkey = os.path.join(userdir, "private.key")
+    pubcert = os.path.join(userdir, "public.cert")
 
     try:
-        os.makedirs(basedir, mode=0o700)
+        # We change the permissions and the owner ship of the base directories
+        # so that cfg.group and cfg.user could read the directories' contents.
+        # See also: https://bugs.python.org/issue42367
+        for bd in [keysdir, orgdir, userdir]:
+            # Allow cfg.group, but not others to read the contents of this group
+            os.makedirs(bd, exist_ok=True)
+            # not using mode= argument to makedirs intentionally - forcing the
+            # permissions we want
+            os.chmod(bd, mode=0o750)
+            os.chown(
+                bd,
+                uid=pwd.getpwnam(TASKD_USER).pw_uid,
+                gid=grp.getgrnam(TASKD_GROUP).gr_gid,
+            )
 
         certtool_cmd("-p", "--bits", CERT_BITS, "--outfile", privkey)
+        os.chmod(privkey, 0o640)
 
         template_data = [
             "organization = {0}".format(org),
@@ -187,7 +207,7 @@ def generate_key(org, user):
                 "--outfile", pubcert
             )
     except:
-        rmtree(basedir)
+        rmtree(userdir)
         raise
 
 
diff --git a/nixos/modules/services/misc/tautulli.nix b/nixos/modules/services/misc/tautulli.nix
index b29e9dc0c8d5f..e379628c8ce63 100644
--- a/nixos/modules/services/misc/tautulli.nix
+++ b/nixos/modules/services/misc/tautulli.nix
@@ -50,14 +50,7 @@ in
         description = lib.mdDoc "Group under which Tautulli runs.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.tautulli;
-        defaultText = literalExpression "pkgs.tautulli";
-        description = lib.mdDoc ''
-          The Tautulli package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "tautulli" { };
     };
   };
 
diff --git a/nixos/modules/services/misc/tp-auto-kbbl.nix b/nixos/modules/services/misc/tp-auto-kbbl.nix
index 8d92d3d936773..f6f2d49733e66 100644
--- a/nixos/modules/services/misc/tp-auto-kbbl.nix
+++ b/nixos/modules/services/misc/tp-auto-kbbl.nix
@@ -9,14 +9,9 @@ in {
 
   options = {
     services.tp-auto-kbbl = {
-      enable = mkEnableOption (lib.mdDoc "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 = lib.mdDoc "Package providing {command}`tp-auto-kbbl`.";
-      };
+      package = mkPackageOption pkgs "tp-auto-kbbl" { };
 
       arguments = mkOption {
         type = types.listOf types.str;
diff --git a/nixos/modules/services/misc/tuxclocker.nix b/nixos/modules/services/misc/tuxclocker.nix
new file mode 100644
index 0000000000000..5969f75b8e30d
--- /dev/null
+++ b/nixos/modules/services/misc/tuxclocker.nix
@@ -0,0 +1,71 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.tuxclocker;
+in
+{
+  options.programs.tuxclocker = {
+    enable = mkEnableOption (lib.mdDoc ''
+      TuxClocker, a hardware control and monitoring program
+    '');
+
+    enableAMD = mkEnableOption (lib.mdDoc ''
+      AMD GPU controls.
+      Sets the `amdgpu.ppfeaturemask` kernel parameter to 0xfffd7fff to enable all TuxClocker controls
+    '');
+
+    enabledNVIDIADevices = mkOption {
+      type = types.listOf types.int;
+      default = [ ];
+      example = [ 0 1 ];
+      description = lib.mdDoc ''
+        Enable NVIDIA GPU controls for a device by index.
+        Sets the `Coolbits` Xorg option to enable all TuxClocker controls.
+      '';
+    };
+
+    useUnfree = mkOption {
+      type = types.bool;
+      default = false;
+      example = true;
+      description = lib.mdDoc ''
+        Whether to use components requiring unfree dependencies.
+        Disabling this allows you to get everything from the binary cache.
+      '';
+    };
+  };
+
+  config = let
+      package = if cfg.useUnfree then pkgs.tuxclocker else pkgs.tuxclocker-without-unfree;
+    in
+      mkIf cfg.enable {
+        environment.systemPackages = [
+          package
+        ];
+
+        services.dbus.packages = [
+          package
+        ];
+
+        # MSR is used for some features
+        boot.kernelModules = [ "msr" ];
+
+        # https://download.nvidia.com/XFree86/Linux-x86_64/430.14/README/xconfigoptions.html#Coolbits
+        services.xserver.config = let
+          configSection = (i: ''
+            Section "Device"
+              Driver "nvidia"
+              Option "Coolbits" "31"
+              Identifier "Device-nvidia[${toString i}]"
+            EndSection
+          '');
+        in
+          concatStrings (map configSection cfg.enabledNVIDIADevices);
+
+        # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/gpu/drm/amd/include/amd_shared.h#n207
+        # Enable everything modifiable in TuxClocker
+        boot.kernelParams = mkIf cfg.enableAMD [ "amdgpu.ppfeaturemask=0xfffd7fff" ];
+      };
+}
diff --git a/nixos/modules/services/misc/xmr-stak.nix b/nixos/modules/services/misc/xmr-stak.nix
index 6e123cf0380cc..54efae48d5d29 100644
--- a/nixos/modules/services/misc/xmr-stak.nix
+++ b/nixos/modules/services/misc/xmr-stak.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.xmr-stak;
 
   pkg = pkgs.xmr-stak.override {
-    inherit (cfg) openclSupport cudaSupport;
+    inherit (cfg) openclSupport;
   };
 
 in
@@ -17,7 +17,6 @@ in
     services.xmr-stak = {
       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;
@@ -64,15 +63,12 @@ in
       wantedBy = [ "multi-user.target" ];
       bindsTo = [ "network-online.target" ];
       after = [ "network-online.target" ];
-      environment = mkIf cfg.cudaSupport {
-        LD_LIBRARY_PATH = "${pkgs.linuxPackages_latest.nvidia_x11}/lib";
-      };
 
       preStart = concatStrings (flip mapAttrsToList cfg.configFiles (fn: content: ''
         ln -sf '${pkgs.writeText "xmr-stak-${fn}" content}' '${fn}'
       ''));
 
-      serviceConfig = let rootRequired = cfg.openclSupport || cfg.cudaSupport; in {
+      serviceConfig = let rootRequired = cfg.openclSupport; in {
         ExecStart = "${pkg}/bin/xmr-stak ${concatStringsSep " " cfg.extraArgs}";
         # xmr-stak generates cpu and/or gpu configuration files
         WorkingDirectory = "/tmp";
diff --git a/nixos/modules/services/misc/xmrig.nix b/nixos/modules/services/misc/xmrig.nix
index d2aa3df45d53d..8ad2d049f8a93 100644
--- a/nixos/modules/services/misc/xmrig.nix
+++ b/nixos/modules/services/misc/xmrig.nix
@@ -15,12 +15,8 @@ with lib;
     services.xmrig = {
       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 = lib.mdDoc "XMRig package to use.";
+      package = mkPackageOption pkgs "xmrig" {
+        example = "xmrig-mo";
       };
 
       settings = mkOption {
@@ -52,15 +48,15 @@ with lib;
   };
 
   config = mkIf cfg.enable {
-    boot.kernelModules = [ "msr" ];
+    hardware.cpu.x86.msr.enable = true;
 
     systemd.services.xmrig = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       description = "XMRig Mining Software Service";
       serviceConfig = {
-        ExecStartPre = "${cfg.package}/bin/xmrig --config=${configFile} --dry-run";
-        ExecStart = "${cfg.package}/bin/xmrig --config=${configFile}";
+        ExecStartPre = "${lib.getExe cfg.package} --config=${configFile} --dry-run";
+        ExecStart = "${lib.getExe cfg.package} --config=${configFile}";
         # https://xmrig.com/docs/miner/randomx-optimization-guide/msr
         # If you use recent XMRig with root privileges (Linux) or admin
         # privileges (Windows) the miner configure all MSR registers
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index b2e4e760d8287..fca03b2ad4e10 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -67,14 +67,14 @@ in {
   options = {
     services.zoneminder = with lib; {
       enable = lib.mkEnableOption (lib.mdDoc ''
-        ZoneMinder
+        ZoneMinder.
 
         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.
+        upgrading to a newer version
       '');
 
       webserver = mkOption {
diff --git a/nixos/modules/services/misc/zookeeper.nix b/nixos/modules/services/misc/zookeeper.nix
index fb51be698e72a..b1c0b80648c68 100644
--- a/nixos/modules/services/misc/zookeeper.nix
+++ b/nixos/modules/services/misc/zookeeper.nix
@@ -103,12 +103,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      description = lib.mdDoc "The zookeeper package to use";
-      default = pkgs.zookeeper;
-      defaultText = literalExpression "pkgs.zookeeper";
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "zookeeper" { };
 
     jre = mkOption {
       description = lib.mdDoc "The JRE with which to run Zookeeper";
diff --git a/nixos/modules/services/monitoring/arbtt.nix b/nixos/modules/services/monitoring/arbtt.nix
index f07ecc5d5dd04..a1a228d6e420d 100644
--- a/nixos/modules/services/monitoring/arbtt.nix
+++ b/nixos/modules/services/monitoring/arbtt.nix
@@ -9,14 +9,7 @@ in {
     services.arbtt = {
       enable = mkEnableOption (lib.mdDoc "Arbtt statistics capture service");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.haskellPackages.arbtt;
-        defaultText = literalExpression "pkgs.haskellPackages.arbtt";
-        description = lib.mdDoc ''
-          The package to use for the arbtt binaries.
-        '';
-      };
+      package = mkPackageOption pkgs [ "haskellPackages" "arbtt" ] { };
 
       logFile = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/monitoring/below.nix b/nixos/modules/services/monitoring/below.nix
index 92ee3882cac87..4a7135162ac44 100644
--- a/nixos/modules/services/monitoring/below.nix
+++ b/nixos/modules/services/monitoring/below.nix
@@ -103,4 +103,6 @@ in {
       };
     };
   };
+
+  meta.maintainers = with lib.maintainers; [ nicoo ];
 }
diff --git a/nixos/modules/services/monitoring/bosun.nix b/nixos/modules/services/monitoring/bosun.nix
index dc75fda6ed8aa..fb412d43ec27d 100644
--- a/nixos/modules/services/monitoring/bosun.nix
+++ b/nixos/modules/services/monitoring/bosun.nix
@@ -24,14 +24,7 @@ in {
 
       enable = mkEnableOption (lib.mdDoc "bosun");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.bosun;
-        defaultText = literalExpression "pkgs.bosun";
-        description = lib.mdDoc ''
-          bosun binary to use.
-        '';
-      };
+      package = mkPackageOption pkgs "bosun" { };
 
       user = mkOption {
         type = types.str;
@@ -108,7 +101,7 @@ in {
           option.
 
           A detailed description of the supported syntax can be found at-spi2-atk
-          http://bosun.org/configuration.html
+          https://bosun.org/configuration.html
         '';
       };
 
diff --git a/nixos/modules/services/monitoring/certspotter.md b/nixos/modules/services/monitoring/certspotter.md
new file mode 100644
index 0000000000000..9bf6e1d946a04
--- /dev/null
+++ b/nixos/modules/services/monitoring/certspotter.md
@@ -0,0 +1,74 @@
+# Cert Spotter {#module-services-certspotter}
+
+Cert Spotter is a tool for monitoring [Certificate Transparency](https://en.wikipedia.org/wiki/Certificate_Transparency)
+logs.
+
+## Service Configuration {#modules-services-certspotter-service-configuration}
+
+A basic config that notifies you of all certificate changes for your
+domain would look as follows:
+
+```nix
+services.certspotter = {
+  enable = true;
+  # replace example.org with your domain name
+  watchlist = [ ".example.org" ];
+  emailRecipients = [ "webmaster@example.org" ];
+};
+
+# Configure an SMTP client
+programs.msmtp.enable = true;
+# Or you can use any other module that provides sendmail, like
+# services.nullmailer, services.opensmtpd, services.postfix
+```
+
+In this case, the leading dot in `".example.org"` means that Cert
+Spotter should monitor not only `example.org`, but also all of its
+subdomains.
+
+## Operation {#modules-services-certspotter-operation}
+
+**By default, NixOS configures Cert Spotter to skip all certificates
+issued before its first launch**, because checking the entire
+Certificate Transparency logs requires downloading tens of terabytes of
+data. If you want to check the *entire* logs for previously issued
+certificates, you have to set `services.certspotter.startAtEnd` to
+`false` and remove all previously saved log state in
+`/var/lib/certspotter/logs`. The downloaded logs aren't saved, so if you
+add a new domain to the watchlist and want Cert Spotter to go through
+the logs again, you will have to remove `/var/lib/certspotter/logs`
+again.
+
+After catching up with the logs, Cert Spotter will start monitoring live
+logs. As of October 2023, it uses around **20 Mbps** of traffic on
+average.
+
+## Hooks {#modules-services-certspotter-hooks}
+
+Cert Spotter supports running custom hooks instead of (or in addition
+to) sending emails. Hooks are shell scripts that will be passed certain
+environment variables.
+
+To see hook documentation, see Cert Spotter's man pages:
+
+```ShellSession
+nix-shell -p certspotter --run 'man 8 certspotter-script'
+```
+
+For example, you can remove `emailRecipients` and send email
+notifications manually using the following hook:
+
+```nix
+services.certspotter.hooks = [
+  (pkgs.writeShellScript "certspotter-hook" ''
+    function print_email() {
+      echo "Subject: [certspotter] $SUMMARY"
+      echo "Mime-Version: 1.0"
+      echo "Content-Type: text/plain; charset=US-ASCII"
+      echo
+      cat "$TEXT_FILENAME"
+    }
+    print_email | ${config.services.certspotter.sendmailPath} -i webmaster@example.org
+  '')
+];
+```
diff --git a/nixos/modules/services/monitoring/certspotter.nix b/nixos/modules/services/monitoring/certspotter.nix
new file mode 100644
index 0000000000000..5551f0e37c512
--- /dev/null
+++ b/nixos/modules/services/monitoring/certspotter.nix
@@ -0,0 +1,143 @@
+{ config
+, lib
+, pkgs
+, ... }:
+
+let
+  cfg = config.services.certspotter;
+
+  configDir = pkgs.linkFarm "certspotter-config" (
+    lib.toList {
+      name = "watchlist";
+      path = pkgs.writeText "certspotter-watchlist" (builtins.concatStringsSep "\n" cfg.watchlist);
+    }
+    ++ lib.optional (cfg.emailRecipients != [ ]) {
+      name = "email_recipients";
+      path = pkgs.writeText "certspotter-email_recipients" (builtins.concatStringsSep "\n" cfg.emailRecipients);
+    }
+    # always generate hooks dir when no emails are provided to allow running cert spotter with no hooks/emails
+    ++ lib.optional (cfg.emailRecipients == [ ] || cfg.hooks != [ ]) {
+      name = "hooks.d";
+      path = pkgs.linkFarm "certspotter-hooks" (lib.imap1 (i: path: {
+        inherit path;
+        name = "hook${toString i}";
+      }) cfg.hooks);
+    });
+in
+{
+  options.services.certspotter = {
+    enable = lib.mkEnableOption "Cert Spotter, a Certificate Transparency log monitor";
+
+    package = lib.mkPackageOption pkgs "certspotter" { };
+
+    startAtEnd = lib.mkOption {
+      type = lib.types.bool;
+      description = ''
+        Whether to skip certificates issued before the first launch of Cert Spotter.
+        Setting this to `false` will cause Cert Spotter to download tens of terabytes of data.
+      '';
+      default = true;
+    };
+
+    sendmailPath = lib.mkOption {
+      type = with lib.types; nullOr path;
+      description = ''
+        Path to the `sendmail` binary. By default, the local sendmail wrapper is used
+        (see {option}`services.mail.sendmailSetuidWrapper`}).
+      '';
+      example = lib.literalExpression ''"''${pkgs.system-sendmail}/bin/sendmail"'';
+    };
+
+    watchlist = lib.mkOption {
+      type = with lib.types; listOf str;
+      description = "Domain names to watch. To monitor a domain with all subdomains, prefix its name with `.` (e.g. `.example.org`).";
+      default = [ ];
+      example = [ ".example.org" "another.example.com" ];
+    };
+
+    emailRecipients = lib.mkOption {
+      type = with lib.types; listOf str;
+      description = "A list of email addresses to send certificate updates to.";
+      default = [ ];
+    };
+
+    hooks = lib.mkOption {
+      type = with lib.types; listOf path;
+      description = ''
+        Scripts to run upon the detection of a new certificate. See `man 8 certspotter-script` or
+        [the GitHub page](https://github.com/SSLMate/certspotter/blob/${pkgs.certspotter.src.rev or "master"}/man/certspotter-script.md)
+        for more info.
+      '';
+      default = [ ];
+      example = lib.literalExpression ''
+        [
+          (pkgs.writeShellScript "certspotter-hook" '''
+            echo "Event summary: $SUMMARY."
+          ''')
+        ]
+      '';
+    };
+
+    extraFlags = lib.mkOption {
+      type = with lib.types; listOf str;
+      description = "Extra command-line arguments to pass to Cert Spotter";
+      example = [ "-no_save" ];
+      default = [ ];
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.emailRecipients != [ ]) -> (cfg.sendmailPath != null);
+        message = ''
+          You must configure the sendmail setuid wrapper (services.mail.sendmailSetuidWrapper)
+          or services.certspotter.sendmailPath
+        '';
+      }
+    ];
+
+    services.certspotter.sendmailPath = let
+      inherit (config.security) wrapperDir;
+      inherit (config.services.mail) sendmailSetuidWrapper;
+    in lib.mkMerge [
+      (lib.mkIf (sendmailSetuidWrapper != null) (lib.mkOptionDefault "${wrapperDir}/${sendmailSetuidWrapper.program}"))
+      (lib.mkIf (sendmailSetuidWrapper == null) (lib.mkOptionDefault null))
+    ];
+
+    users.users.certspotter = {
+      description = "Cert Spotter user";
+      group = "certspotter";
+      home = "/var/lib/certspotter";
+      isSystemUser = true;
+    };
+    users.groups.certspotter = { };
+
+    systemd.services.certspotter = {
+      description = "Cert Spotter - Certificate Transparency Monitor";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.CERTSPOTTER_CONFIG_DIR = configDir;
+      environment.SENDMAIL_PATH = if cfg.sendmailPath != null then cfg.sendmailPath else "/run/current-system/sw/bin/false";
+      script = ''
+        export CERTSPOTTER_STATE_DIR="$STATE_DIRECTORY"
+        cd "$CERTSPOTTER_STATE_DIR"
+        ${lib.optionalString cfg.startAtEnd ''
+          if [[ ! -d logs ]]; then
+            # Don't download certificates issued before the first launch
+            exec ${cfg.package}/bin/certspotter -start_at_end ${lib.escapeShellArgs cfg.extraFlags}
+          fi
+        ''}
+        exec ${cfg.package}/bin/certspotter ${lib.escapeShellArgs cfg.extraFlags}
+      '';
+      serviceConfig = {
+        User = "certspotter";
+        Group = "certspotter";
+        StateDirectory = "certspotter";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ chayleaf ];
+  meta.doc = ./certspotter.md;
+}
diff --git a/nixos/modules/services/monitoring/cockpit.nix b/nixos/modules/services/monitoring/cockpit.nix
index 2947b4d801201..45389a3174e11 100644
--- a/nixos/modules/services/monitoring/cockpit.nix
+++ b/nixos/modules/services/monitoring/cockpit.nix
@@ -2,14 +2,14 @@
 
 let
   cfg = config.services.cockpit;
-  inherit (lib) types mkEnableOption mkOption mkIf mdDoc literalMD mkPackageOptionMD;
+  inherit (lib) types mkEnableOption mkOption mkIf mdDoc literalMD mkPackageOption;
   settingsFormat = pkgs.formats.ini {};
 in {
   options = {
     services.cockpit = {
       enable = mkEnableOption (mdDoc "Cockpit");
 
-      package = mkPackageOptionMD pkgs "Cockpit" {
+      package = mkPackageOption pkgs "Cockpit" {
         default = [ "cockpit" ];
       };
 
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index 5d525995c67a7..3e62ef422bade 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -41,14 +41,7 @@ in {
       type = types.bool;
     };
 
-    package = mkOption {
-      default = pkgs.collectd;
-      defaultText = literalExpression "pkgs.collectd";
-      description = lib.mdDoc ''
-        Which collectd package to use.
-      '';
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "collectd" { };
 
     buildMinimalPackage = mkOption {
       default = false;
diff --git a/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix
index 1736b0c088a37..7b07c80c8d7be 100644
--- a/nixos/modules/services/monitoring/datadog-agent.nix
+++ b/nixos/modules/services/monitoring/datadog-agent.nix
@@ -51,16 +51,13 @@ in {
   options.services.datadog-agent = {
     enable = mkEnableOption (lib.mdDoc "Datadog-agent v7 monitoring service");
 
-    package = mkOption {
-      default = pkgs.datadog-agent;
-      defaultText = literalExpression "pkgs.datadog-agent";
-      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
-        checks.
+    package = mkPackageOption pkgs "datadog-agent" {
+      extraDescription = ''
+        ::: {.note}
+        The provided package is expected to have an overridable `pythonPackages`-attribute
+        which configures the Python environment with the Datadog checks.
+        :::
       '';
-      type = types.package;
     };
 
     apiKeyFile = mkOption {
diff --git a/nixos/modules/services/monitoring/goss.md b/nixos/modules/services/monitoring/goss.md
new file mode 100644
index 0000000000000..1e636aa3bdf33
--- /dev/null
+++ b/nixos/modules/services/monitoring/goss.md
@@ -0,0 +1,44 @@
+# Goss {#module-services-goss}
+
+[goss](https://goss.rocks/) is a YAML based serverspec alternative tool
+for validating a server's configuration.
+
+## Basic Usage {#module-services-goss-basic-usage}
+
+A minimal configuration looks like this:
+
+```
+{
+  services.goss = {
+    enable = true;
+
+    environment = {
+      GOSS_FMT = "json";
+      GOSS_LOGLEVEL = "TRACE";
+    };
+
+    settings = {
+      addr."tcp://localhost:8080" = {
+        reachable = true;
+        local-address = "127.0.0.1";
+      };
+      command."check-goss-version" = {
+        exec = "${lib.getExe pkgs.goss} --version";
+        exit-status = 0;
+      };
+      dns.localhost.resolvable = true;
+      file."/nix" = {
+        filetype = "directory";
+        exists = true;
+      };
+      group.root.exists = true;
+      kernel-param."kernel.ostype".value = "Linux";
+      service.goss = {
+        enabled = true;
+        running = true;
+      };
+      user.root.exists = true;
+    };
+  };
+}
+```
diff --git a/nixos/modules/services/monitoring/goss.nix b/nixos/modules/services/monitoring/goss.nix
new file mode 100644
index 0000000000000..1b973bbbf45cc
--- /dev/null
+++ b/nixos/modules/services/monitoring/goss.nix
@@ -0,0 +1,86 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.goss;
+
+  settingsFormat = pkgs.formats.yaml { };
+  configFile = settingsFormat.generate "goss.yaml" cfg.settings;
+
+in {
+  meta = {
+    doc = ./goss.md;
+    maintainers = [ lib.maintainers.anthonyroussel ];
+  };
+
+  options = {
+    services.goss = {
+      enable = lib.mkEnableOption (lib.mdDoc "Goss daemon");
+
+      package = lib.mkPackageOption pkgs "goss" { };
+
+      environment = lib.mkOption {
+        type = lib.types.attrsOf lib.types.str;
+        default = { };
+        example = {
+          GOSS_FMT = "json";
+          GOSS_LOGLEVEL = "FATAL";
+          GOSS_LISTEN = ":8080";
+        };
+        description = lib.mdDoc ''
+          Environment variables to set for the goss service.
+
+          See <https://github.com/goss-org/goss/blob/master/docs/manual.md>
+        '';
+      };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule { freeformType = settingsFormat.type; };
+        default = { };
+        example = {
+          addr."tcp://localhost:8080" = {
+            reachable = true;
+            local-address = "127.0.0.1";
+          };
+          service.goss = {
+            enabled = true;
+            running = true;
+          };
+        };
+        description = lib.mdDoc ''
+          The global options in `config` file in yaml format.
+
+          Refer to <https://github.com/goss-org/goss/blob/master/docs/goss-json-schema.yaml> for schema.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.goss = {
+      description = "Goss - Quick and Easy server validation";
+      unitConfig.Documentation = "https://github.com/goss-org/goss/blob/master/docs/manual.md";
+
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+
+      environment = {
+        GOSS_FILE = configFile;
+      } // cfg.environment;
+
+      reloadTriggers = [ configFile ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStart = "${cfg.package}/bin/goss serve";
+        Group = "goss";
+        Restart = "on-failure";
+        RestartSec = 5;
+        User = "goss";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/grafana-agent.nix b/nixos/modules/services/monitoring/grafana-agent.nix
index 13604ff77c686..e8d38a4531763 100644
--- a/nixos/modules/services/monitoring/grafana-agent.nix
+++ b/nixos/modules/services/monitoring/grafana-agent.nix
@@ -13,7 +13,7 @@ in
   options.services.grafana-agent = {
     enable = mkEnableOption (lib.mdDoc "grafana-agent");
 
-    package = mkPackageOptionMD pkgs "grafana-agent" { };
+    package = mkPackageOption pkgs "grafana-agent" { };
 
     credentials = mkOption {
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/monitoring/grafana-image-renderer.nix b/nixos/modules/services/monitoring/grafana-image-renderer.nix
index 36258866646ab..afe9eb4d7b959 100644
--- a/nixos/modules/services/monitoring/grafana-image-renderer.nix
+++ b/nixos/modules/services/monitoring/grafana-image-renderer.nix
@@ -108,7 +108,7 @@ in {
 
     services.grafana.settings.rendering = mkIf cfg.provisionGrafana {
       server_url = "http://localhost:${toString cfg.settings.service.port}/render";
-      callback_url = "http://localhost:${toString config.services.grafana.settings.server.http_port}";
+      callback_url = "http://${config.services.grafana.settings.server.http_addr}:${toString config.services.grafana.settings.server.http_port}";
     };
 
     services.grafana-image-renderer.chromium = mkDefault pkgs.chromium;
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 571b9a3aeebd1..5ac010bf81ee8 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -74,7 +74,7 @@ let
     fi
   '';
   provisionConfDir = pkgs.runCommand "grafana-provisioning" { nativeBuildInputs = [ pkgs.xorg.lndir ]; } ''
-    mkdir -p $out/{datasources,dashboards,notifiers,alerting}
+    mkdir -p $out/{alerting,datasources,dashboards,notifiers,plugins}
     ${ln { src = datasourceFileOrDir;    dir = "datasources"; filename = "datasource"; }}
     ${ln { src = dashboardFileOrDir;     dir = "dashboards";  filename = "dashboard"; }}
     ${ln { src = notifierFileOrDir;      dir = "notifiers";   filename = "notifier"; }}
@@ -88,27 +88,7 @@ let
   # 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
+  # https://grafana.com/docs/grafana/latest/administration/provisioning/#datasources
   grafanaTypes.datasourceConfig = types.submodule {
     freeformType = provisioningSettingsFormat.type;
 
@@ -160,7 +140,7 @@ let
     };
   };
 
-  # http://docs.grafana.org/administration/provisioning/#dashboards
+  # https://grafana.com/docs/grafana/latest/administration/provisioning/#dashboards
   grafanaTypes.dashboardConfig = types.submodule {
     freeformType = provisioningSettingsFormat.type;
 
@@ -330,12 +310,7 @@ 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;
-    };
+    package = mkPackageOption pkgs "grafana" { };
 
     dataDir = mkOption {
       description = lib.mdDoc "Data directory.";
@@ -1160,7 +1135,7 @@ in
           Declaratively provision Grafana's datasources.
         '';
         default = { };
-        type = submodule' {
+        type = types.submodule {
           options.settings = mkOption {
             description = lib.mdDoc ''
               Grafana datasource configuration in Nix. Can't be used with
@@ -1235,7 +1210,7 @@ in
           Declaratively provision Grafana's dashboards.
         '';
         default = { };
-        type = submodule' {
+        type = types.submodule {
           options.settings = mkOption {
             description = lib.mdDoc ''
               Grafana dashboard configuration in Nix. Can't be used with
@@ -1856,11 +1831,12 @@ in
         set -o errexit -o pipefail -o nounset -o errtrace
         shopt -s inherit_errexit
 
-        exec ${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir} -config ${configFile}
+        exec ${cfg.package}/bin/grafana server -homepath ${cfg.dataDir} -config ${configFile}
       '';
       serviceConfig = {
         WorkingDirectory = cfg.dataDir;
         User = "grafana";
+        Restart = "on-failure";
         RuntimeDirectory = "grafana";
         RuntimeDirectoryMode = "0755";
         # Hardening
diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix
index 65c91b8f79bb6..cc3d70976204d 100644
--- a/nixos/modules/services/monitoring/graphite.nix
+++ b/nixos/modules/services/monitoring/graphite.nix
@@ -102,7 +102,7 @@ in {
         default = "";
         description = lib.mdDoc ''
           Graphite webapp settings. See:
-          <http://graphite.readthedocs.io/en/latest/config-local-settings.html>
+          <https://graphite.readthedocs.io/en/latest/config-local-settings.html>
         '';
       };
     };
diff --git a/nixos/modules/services/monitoring/heapster.nix b/nixos/modules/services/monitoring/heapster.nix
index fc63276b62f73..9f9c24949fc98 100644
--- a/nixos/modules/services/monitoring/heapster.nix
+++ b/nixos/modules/services/monitoring/heapster.nix
@@ -26,12 +26,7 @@ in {
       type = types.separatedString " ";
     };
 
-    package = mkOption {
-      description = lib.mdDoc "Package to use by heapster";
-      default = pkgs.heapster;
-      defaultText = literalExpression "pkgs.heapster";
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "heapster" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/monitoring/karma.nix b/nixos/modules/services/monitoring/karma.nix
index 85dbc81f443f0..9883ec4fe8414 100644
--- a/nixos/modules/services/monitoring/karma.nix
+++ b/nixos/modules/services/monitoring/karma.nix
@@ -8,14 +8,7 @@ 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.
-      '';
-    };
+    package = mkPackageOption pkgs "karma" { };
 
     configFile = mkOption {
       type = types.path;
diff --git a/nixos/modules/services/monitoring/kthxbye.nix b/nixos/modules/services/monitoring/kthxbye.nix
index 3f988dcb722fe..3be0024457220 100644
--- a/nixos/modules/services/monitoring/kthxbye.nix
+++ b/nixos/modules/services/monitoring/kthxbye.nix
@@ -9,14 +9,7 @@ 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.
-      '';
-    };
+    package = mkPackageOption pkgs "kthxbye" { };
 
     openFirewall = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/monitoring/librenms.nix b/nixos/modules/services/monitoring/librenms.nix
new file mode 100644
index 0000000000000..08a46754e0e86
--- /dev/null
+++ b/nixos/modules/services/monitoring/librenms.nix
@@ -0,0 +1,624 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.librenms;
+  settingsFormat = pkgs.formats.json {};
+  configJson = settingsFormat.generate "librenms-config.json" cfg.settings;
+
+  package = pkgs.librenms.override {
+    logDir = cfg.logDir;
+    dataDir = cfg.dataDir;
+  };
+
+  phpOptions = ''
+    log_errors = on
+    post_max_size = 100M
+    upload_max_filesize = 100M
+    date.timezone = "${config.time.timeZone}"
+  '';
+  phpIni = pkgs.runCommand "php.ini" {
+    inherit (package) phpPackage;
+    inherit phpOptions;
+    preferLocalBuild = true;
+    passAsFile = [ "phpOptions" ];
+  } ''
+    cat $phpPackage/etc/php.ini $phpOptionsPath > $out
+  '';
+
+  artisanWrapper = pkgs.writeShellScriptBin "librenms-artisan" ''
+    cd ${package}
+    sudo=exec
+    if [[ "$USER" != ${cfg.user} ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}'
+    fi
+    $sudo ${package}/artisan $*
+  '';
+
+  lnmsWrapper = pkgs.writeShellScriptBin "lnms" ''
+    cd ${package}
+    exec ${package}/lnms $*
+  '';
+
+  configFile = pkgs.writeText "config.php" ''
+    <?php
+    $new_config = json_decode(file_get_contents("${cfg.dataDir}/config.json"), true);
+    $config = ($config == null) ? $new_config : array_merge($config, $new_config);
+
+    ${lib.optionalString (cfg.extraConfig != null) cfg.extraConfig}
+  '';
+
+in {
+  options.services.librenms = with lib; {
+    enable = mkEnableOption "LibreNMS network monitoring system";
+
+    user = mkOption {
+      type = types.str;
+      default = "librenms";
+      description = ''
+        Name of the LibreNMS user.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "librenms";
+      description = ''
+        Name of the LibreNMS group.
+      '';
+    };
+
+    hostname = mkOption {
+      type = types.str;
+      default = config.networking.fqdnOrHostName;
+      defaultText = literalExpression "config.networking.fqdnOrHostName";
+      description = ''
+        The hostname to serve LibreNMS on.
+      '';
+    };
+
+    pollerThreads = mkOption {
+      type = types.int;
+      default = 16;
+      description = ''
+        Amount of threads of the cron-poller.
+      '';
+    };
+
+    enableOneMinutePolling = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enables the [1-Minute Polling](https://docs.librenms.org/Support/1-Minute-Polling/).
+        Changing this option will automatically convert your existing rrd files.
+      '';
+    };
+
+    useDistributedPollers = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enables (distributed pollers)[https://docs.librenms.org/Extensions/Distributed-Poller/]
+        for this LibreNMS instance. This will enable a local `rrdcached` and `memcached` server.
+
+        To use this feature, make sure to configure your firewall that the distributed pollers
+        can reach the local `mysql`, `rrdcached` and `memcached` ports.
+      '';
+    };
+
+    distributedPoller = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Configure this LibreNMS instance as a (distributed poller)[https://docs.librenms.org/Extensions/Distributed-Poller/].
+          This will disable all web features and just configure the poller features.
+          Use the `mysql` database of your main LibreNMS instance in the database settings.
+        '';
+      };
+
+      name = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Custom name of this poller.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "0";
+        example = "1,2";
+        description = ''
+          Group(s) of this poller.
+        '';
+      };
+
+      distributedBilling = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable distributed billing on this poller.
+        '';
+      };
+
+      memcachedHost = mkOption {
+        type = types.str;
+        description = ''
+          Hostname or IP of the `memcached` server.
+        '';
+      };
+
+      memcachedPort = mkOption {
+        type = types.port;
+        default = 11211;
+        description = ''
+          Port of the `memcached` server.
+        '';
+      };
+
+      rrdcachedHost = mkOption {
+        type = types.str;
+        description = ''
+          Hostname or IP of the `rrdcached` server.
+        '';
+      };
+
+      rrdcachedPort = mkOption {
+        type = types.port;
+        default = 42217;
+        description = ''
+          Port of the `memcached` server.
+        '';
+      };
+    };
+
+    poolConfig = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = ''
+        Options for the LibreNMS 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 = [
+            "librenms.''${config.networking.domain}"
+          ];
+          # To enable encryption and let let's encrypt take care of certificate
+          forceSSL = true;
+          enableACME = true;
+          # To set the LibreNMS virtualHost as the default virtualHost;
+          default = true;
+        }
+      '';
+      description = ''
+        With this option, you can customize the nginx virtualHost settings.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/librenms";
+      description = ''
+        Path of the LibreNMS state directory.
+      '';
+    };
+
+    logDir = mkOption {
+      type = types.path;
+      default = "/var/log/librenms";
+      description = ''
+        Path of the LibreNMS logging directory.
+      '';
+    };
+
+    database = {
+      createLocally = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to create a local database automatically.
+        '';
+      };
+
+      host = mkOption {
+        default = "localhost";
+        description = ''
+          Hostname or IP of the MySQL/MariaDB server.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 3306;
+        description = ''
+          Port of the MySQL/MariaDB server.
+        '';
+      };
+
+      database = mkOption {
+        type = types.str;
+        default = "librenms";
+        description = ''
+          Name of the database on the MySQL/MariaDB server.
+        '';
+      };
+
+      username = mkOption {
+        type = types.str;
+        default = "librenms";
+        description = ''
+          Name of the user on the MySQL/MariaDB server.
+        '';
+      };
+
+      passwordFile = mkOption {
+        type = types.path;
+        example = "/run/secrets/mysql.pass";
+        description = ''
+          A file containing the password for the user of the MySQL/MariaDB server.
+          Must be readable for the LibreNMS user.
+        '';
+      };
+    };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        File containing env-vars to be substituted into the final config. Useful for secrets.
+        Does not apply to settings defined in `extraConfig`.
+      '';
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {};
+      };
+      description = ''
+        Attrset of the LibreNMS configuration.
+        See https://docs.librenms.org/Support/Configuration/ for reference.
+        All possible options are listed [here](https://github.com/librenms/librenms/blob/master/misc/config_definitions.json).
+        See https://docs.librenms.org/Extensions/Authentication/ for setting other authentication methods.
+      '';
+      default = { };
+      example = {
+        base_url = "/librenms/";
+        top_devices = true;
+        top_ports = false;
+      };
+    };
+
+    extraConfig = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Additional config for LibreNMS that will be appended to the `config.php`. See
+        https://github.com/librenms/librenms/blob/master/misc/config_definitions.json
+        for possible options. Useful if you want to use PHP-Functions in your config.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = config.time.timeZone != null;
+        message = "You must set `time.timeZone` to use the LibreNMS module.";
+      }
+      {
+        assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
+        message = "The database host must be \"localhost\" if services.librenms.database.createLocally is set to true.";
+      }
+      {
+        assertion = !(cfg.useDistributedPollers && cfg.distributedPoller.enable);
+        message = "The LibreNMS instance can't be a distributed poller and a full instance at the same time.";
+      }
+    ];
+
+    users.users.${cfg.user} = {
+      group = "${cfg.group}";
+      isSystemUser = true;
+    };
+
+    users.groups.${cfg.group} = { };
+
+    services.librenms.settings = {
+      # basic configs
+      "user" = cfg.user;
+      "own_hostname" = cfg.hostname;
+      "base_url" = lib.mkDefault "/";
+      "auth_mechanism" = lib.mkDefault "mysql";
+
+      # disable auto update function (won't work with NixOS)
+      "update" = false;
+
+      # enable fast ping by default
+      "ping_rrd_step" = 60;
+
+      # one minute polling
+      "rrd.step" = if cfg.enableOneMinutePolling then 60 else 300;
+      "rrd.heartbeat" = if cfg.enableOneMinutePolling then 120 else 600;
+    } // (lib.optionalAttrs cfg.distributedPoller.enable {
+      "distributed_poller" = true;
+      "distributed_poller_name" = lib.mkIf (cfg.distributedPoller.name != null) cfg.distributedPoller.name;
+      "distributed_poller_group" = cfg.distributedPoller.group;
+      "distributed_billing" = cfg.distributedPoller.distributedBilling;
+      "distributed_poller_memcached_host" = cfg.distributedPoller.memcachedHost;
+      "distributed_poller_memcached_port" = cfg.distributedPoller.memcachedPort;
+      "rrdcached" = "${cfg.distributedPoller.rrdcachedHost}:${toString cfg.distributedPoller.rrdcachedPort}";
+    }) // (lib.optionalAttrs cfg.useDistributedPollers {
+      "distributed_poller" = true;
+      # still enable a local poller with distributed polling
+      "distributed_poller_group" = lib.mkDefault "0";
+      "distributed_billing" = lib.mkDefault true;
+      "distributed_poller_memcached_host" = "localhost";
+      "distributed_poller_memcached_port" = 11211;
+      "rrdcached" = "localhost:42217";
+    });
+
+    services.memcached = lib.mkIf cfg.useDistributedPollers {
+      enable = true;
+      listen = "0.0.0.0";
+    };
+
+    systemd.services.rrdcached = lib.mkIf cfg.useDistributedPollers {
+      description = "rrdcached";
+      after = [ "librenms-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "forking";
+        User = cfg.user;
+        Group = cfg.group;
+        LimitNOFILE = 16384;
+        RuntimeDirectory = "rrdcached";
+        PidFile = "/run/rrdcached/rrdcached.pid";
+        # rrdcached params from https://docs.librenms.org/Extensions/Distributed-Poller/#config-sample
+        ExecStart = "${pkgs.rrdtool}/bin/rrdcached -l 0:42217 -R -j ${cfg.dataDir}/rrdcached-journal/ -F -b ${cfg.dataDir}/rrd -B -w 1800 -z 900 -p /run/rrdcached/rrdcached.pid";
+      };
+    };
+
+    services.mysql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      package = lib.mkDefault pkgs.mariadb;
+      settings.mysqld = {
+        innodb_file_per_table = 1;
+        lower_case_table_names = 0;
+      } // (lib.optionalAttrs cfg.useDistributedPollers {
+        bind-address = "0.0.0.0";
+      });
+      ensureDatabases = [ cfg.database.database ];
+      ensureUsers = [
+        {
+          name = cfg.database.username;
+          ensurePermissions = {
+            "${cfg.database.database}.*" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+      initialScript = lib.mkIf cfg.useDistributedPollers (pkgs.writeText "mysql-librenms-init" ''
+        CREATE USER IF NOT EXISTS '${cfg.database.username}'@'%';
+        GRANT ALL PRIVILEGES ON ${cfg.database.database}.* TO '${cfg.database.username}'@'%';
+      '');
+    };
+
+    services.nginx = lib.mkIf (!cfg.distributedPoller.enable) {
+      enable = true;
+      virtualHosts."${cfg.hostname}" = lib.mkMerge [
+        cfg.nginx
+        {
+          root = lib.mkForce "${package}/html";
+          locations."/" = {
+            index = "index.php";
+            tryFiles = "$uri $uri/ /index.php?$query_string";
+          };
+          locations."~ .php$".extraConfig = ''
+            fastcgi_pass unix:${config.services.phpfpm.pools."librenms".socket};
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+          '';
+        }
+      ];
+    };
+
+    services.phpfpm.pools.librenms = lib.mkIf (!cfg.distributedPoller.enable) {
+      user = cfg.user;
+      group = cfg.group;
+      inherit (package) phpPackage;
+      inherit phpOptions;
+      settings = {
+        "listen.mode" = "0660";
+        "listen.owner" = config.services.nginx.user;
+        "listen.group" = config.services.nginx.group;
+      } // cfg.poolConfig;
+    };
+
+    systemd.services.librenms-scheduler = {
+      description = "LibreNMS Scheduler";
+      path = [ pkgs.unixtools.whereis ];
+      serviceConfig = {
+        Type = "oneshot";
+        WorkingDirectory = package;
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${artisanWrapper}/bin/librenms-artisan schedule:run";
+      };
+    };
+
+    systemd.timers.librenms-scheduler = {
+      description = "LibreNMS Scheduler";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "minutely";
+        AccuracySec = "1second";
+      };
+    };
+
+    systemd.services.librenms-setup = {
+      description = "Preparation tasks for LibreNMS";
+      before = [ "phpfpm-librenms.service" ];
+      after = [ "systemd-tmpfiles-setup.service" ]
+        ++ (lib.optional (cfg.database.host == "localhost") "mysql.service");
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ package configFile ];
+      path = [ pkgs.mariadb pkgs.unixtools.whereis pkgs.gnused ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStartPre = lib.mkIf cfg.database.createLocally [ "!${pkgs.writeShellScript "librenms-db-init" ''
+          DB_PASSWORD=$(cat ${cfg.database.passwordFile} | tr -d '\n')
+          echo "ALTER USER '${cfg.database.username}'@'localhost' IDENTIFIED BY '$DB_PASSWORD';" | ${pkgs.mariadb}/bin/mysql
+          ${lib.optionalString cfg.useDistributedPollers ''
+            echo "ALTER USER '${cfg.database.username}'@'%' IDENTIFIED BY '$DB_PASSWORD';" | ${pkgs.mariadb}/bin/mysql
+          ''}
+        ''}"];
+      };
+      script = ''
+        set -euo pipefail
+
+        # config setup
+        ln -sf ${configFile} ${cfg.dataDir}/config.php
+        ${pkgs.envsubst}/bin/envsubst -i ${configJson} -o ${cfg.dataDir}/config.json
+        export PHPRC=${phpIni}
+
+        if [[ ! -s ${cfg.dataDir}/.env ]]; then
+          # init .env file
+          echo "APP_KEY=" > ${cfg.dataDir}/.env
+          ${artisanWrapper}/bin/librenms-artisan key:generate --ansi
+          ${artisanWrapper}/bin/librenms-artisan webpush:vapid
+          echo "" >> ${cfg.dataDir}/.env
+          echo -n "NODE_ID=" >> ${cfg.dataDir}/.env
+          ${package.phpPackage}/bin/php -r "echo uniqid();" >> ${cfg.dataDir}/.env
+          echo "" >> ${cfg.dataDir}/.env
+        else
+          # .env file already exists --> only update database and cache config
+          ${pkgs.gnused}/bin/sed -i /^DB_/d ${cfg.dataDir}/.env
+          ${pkgs.gnused}/bin/sed -i /^CACHE_DRIVER/d ${cfg.dataDir}/.env
+        fi
+        ${lib.optionalString (cfg.useDistributedPollers || cfg.distributedPoller.enable) ''
+          echo "CACHE_DRIVER=memcached" >> ${cfg.dataDir}/.env
+        ''}
+        echo "DB_HOST=${cfg.database.host}" >> ${cfg.dataDir}/.env
+        echo "DB_PORT=${toString cfg.database.port}" >> ${cfg.dataDir}/.env
+        echo "DB_DATABASE=${cfg.database.database}" >> ${cfg.dataDir}/.env
+        echo "DB_USERNAME=${cfg.database.username}" >> ${cfg.dataDir}/.env
+        echo -n "DB_PASSWORD=" >> ${cfg.dataDir}/.env
+        cat ${cfg.database.passwordFile} >> ${cfg.dataDir}/.env
+
+        # clear cache after update
+        OLD_VERSION=$(cat ${cfg.dataDir}/version)
+        if [[ $OLD_VERSION != "${package.version}" ]]; then
+          rm -r ${cfg.dataDir}/cache/*
+          echo "${package.version}" > ${cfg.dataDir}/version
+        fi
+
+        # convert rrd files when the oneMinutePolling option is changed
+        OLD_ENABLED=$(cat ${cfg.dataDir}/one_minute_enabled)
+        if [[ $OLD_ENABLED != "${lib.boolToString cfg.enableOneMinutePolling}" ]]; then
+          ${package}/scripts/rrdstep.php -h all
+          echo "${lib.boolToString cfg.enableOneMinutePolling}" > ${cfg.dataDir}/one_minute_enabled
+        fi
+
+        # migrate db
+        ${artisanWrapper}/bin/librenms-artisan migrate --force --no-interaction
+      '';
+    };
+
+    programs.mtr.enable = true;
+
+    services.logrotate = {
+      enable = true;
+      settings."${cfg.logDir}/librenms.log" = {
+        su = "${cfg.user} ${cfg.group}";
+        create = "0640 ${cfg.user} ${cfg.group}";
+        rotate = 6;
+        frequency = "weekly";
+        compress = true;
+        delaycompress = true;
+        missingok = true;
+        notifempty = true;
+      };
+    };
+
+    services.cron = {
+      enable = true;
+      systemCronJobs = let
+        env = "PHPRC=${phpIni}";
+      in [
+        # based on crontab provided by LibreNMS
+        "33 */6 * * * ${cfg.user} ${env} ${package}/cronic ${package}/discovery-wrapper.py 1"
+        "*/5 * * * * ${cfg.user} ${env} ${package}/discovery.php -h new >> /dev/null 2>&1"
+
+        "${if cfg.enableOneMinutePolling then "*" else "*/5"} * * * * ${cfg.user} ${env} ${package}/cronic ${package}/poller-wrapper.py ${toString cfg.pollerThreads}"
+        "* * * * * ${cfg.user} ${env} ${package}/alerts.php >> /dev/null 2>&1"
+
+        "*/5 * * * * ${cfg.user} ${env} ${package}/poll-billing.php >> /dev/null 2>&1"
+        "01 * * * * ${cfg.user} ${env} ${package}/billing-calculate.php >> /dev/null 2>&1"
+        "*/5 * * * * ${cfg.user} ${env} ${package}/check-services.php >> /dev/null 2>&1"
+
+        # extra: fast ping
+        "* * * * * ${cfg.user} ${env} ${package}/ping.php >> /dev/null 2>&1"
+
+        # daily.sh tasks are split to exclude update
+        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh cleanup >> /dev/null 2>&1"
+        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh notifications >> /dev/null 2>&1"
+        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh peeringdb >> /dev/null 2>&1"
+        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh mac_oui >> /dev/null 2>&1"
+      ];
+    };
+
+    security.wrappers = {
+      fping = {
+        setuid = true;
+        owner = "root";
+        group = "root";
+        source = "${pkgs.fping}/bin/fping";
+      };
+    };
+
+    environment.systemPackages = [ artisanWrapper lnmsWrapper ];
+
+    systemd.tmpfiles.rules = [
+      "d ${cfg.logDir}                               0750 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.logDir}/librenms.log                  0640 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}                              0750 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.dataDir}/.env                         0600 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.dataDir}/version                      0600 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.dataDir}/one_minute_enabled           0600 ${cfg.user} ${cfg.group} - -"
+      "f ${cfg.dataDir}/config.json                  0600 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage                      0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/app                  0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/debugbar             0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/framework            0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/framework/cache      0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/framework/sessions   0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/framework/views      0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/storage/logs                 0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/rrd                          0700 ${cfg.user} ${cfg.group} - -"
+      "d ${cfg.dataDir}/cache                        0700 ${cfg.user} ${cfg.group} - -"
+    ] ++ lib.optionals cfg.useDistributedPollers [
+      "d ${cfg.dataDir}/rrdcached-journal            0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+  };
+
+  meta.maintainers = lib.teams.wdz.members;
+}
diff --git a/nixos/modules/services/monitoring/loki.nix b/nixos/modules/services/monitoring/loki.nix
index f3b97e9151eaf..fade3c4fbad3e 100644
--- a/nixos/modules/services/monitoring/loki.nix
+++ b/nixos/modules/services/monitoring/loki.nix
@@ -22,7 +22,7 @@ in {
       '';
     };
 
-    package = lib.mkPackageOptionMD pkgs "grafana-loki" { };
+    package = lib.mkPackageOption pkgs "grafana-loki" { };
 
     group = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/monitoring/mackerel-agent.nix b/nixos/modules/services/monitoring/mackerel-agent.nix
index 67dc1bc19edd8..5915634ed26fe 100644
--- a/nixos/modules/services/monitoring/mackerel-agent.nix
+++ b/nixos/modules/services/monitoring/mackerel-agent.nix
@@ -11,10 +11,10 @@ in {
 
     # the upstream package runs as root, but doesn't seem to be strictly
     # necessary for basic functionality
-    runAsRoot = mkEnableOption (lib.mdDoc "Whether to run as root");
+    runAsRoot = mkEnableOption (lib.mdDoc "running as root");
 
     autoRetirement = mkEnableOption (lib.mdDoc ''
-      Whether to automatically retire the host upon OS shutdown.
+      retiring the host upon OS shutdown
     '');
 
     apiKeyFile = mkOption {
@@ -59,7 +59,7 @@ in {
         };
 
         options.diagnostic =
-          mkEnableOption (lib.mdDoc "Collect memory usage for the agent itself");
+          mkEnableOption (lib.mdDoc "collecting memory usage for the agent itself");
       };
     };
   };
@@ -84,6 +84,7 @@ in {
     # upstream service file in https://git.io/JUt4Q
     systemd.services.mackerel-agent = {
       description = "mackerel.io agent";
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" "nss-lookup.target" ];
       wantedBy = [ "multi-user.target" ];
       environment = {
diff --git a/nixos/modules/services/monitoring/metricbeat.nix b/nixos/modules/services/monitoring/metricbeat.nix
index 310c9d8ed5094..c3320f6955645 100644
--- a/nixos/modules/services/monitoring/metricbeat.nix
+++ b/nixos/modules/services/monitoring/metricbeat.nix
@@ -5,6 +5,7 @@ let
     attrValues
     literalExpression
     mkEnableOption
+    mkPackageOption
     mkIf
     mkOption
     types
@@ -21,14 +22,8 @@ in
 
       enable = mkEnableOption (lib.mdDoc "metricbeat");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.metricbeat;
-        defaultText = literalExpression "pkgs.metricbeat";
-        example = literalExpression "pkgs.metricbeat7";
-        description = lib.mdDoc ''
-          The metricbeat package to use
-        '';
+      package = mkPackageOption pkgs "metricbeat" {
+        example = "metricbeat7";
       };
 
       modules = mkOption {
diff --git a/nixos/modules/services/monitoring/mimir.nix b/nixos/modules/services/monitoring/mimir.nix
index edca9b7be4ff0..117cbf6a4a8c1 100644
--- a/nixos/modules/services/monitoring/mimir.nix
+++ b/nixos/modules/services/monitoring/mimir.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) escapeShellArgs mkEnableOption mkIf mkOption types;
+  inherit (lib) escapeShellArgs mkEnableOption mkPackageOption mkIf mkOption types;
 
   cfg = config.services.mimir;
 
@@ -26,17 +26,22 @@ in {
       '';
     };
 
-    package = mkOption {
-      default = pkgs.mimir;
-      defaultText = lib.literalExpression "pkgs.mimir";
-      type = types.package;
-      description = lib.mdDoc ''Mimir package to use.'';
+    package = mkPackageOption pkgs "mimir" { };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "--config.expand-env=true" ];
+      description = lib.mdDoc ''
+        Specify a list of additional command line flags,
+        which get escaped and are then passed to Mimir.
+      '';
     };
   };
 
   config = mkIf cfg.enable {
     # for mimirtool
-    environment.systemPackages = [ pkgs.mimir ];
+    environment.systemPackages = [ cfg.package ];
 
     assertions = [{
       assertion = (
@@ -60,7 +65,7 @@ in {
                else cfg.configFile;
       in
       {
-        ExecStart = "${cfg.package}/bin/mimir --config.file=${conf}";
+        ExecStart = "${cfg.package}/bin/mimir --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
         DynamicUser = true;
         Restart = "always";
         ProtectSystem = "full";
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index f37f2689927ed..5ed7cac48ae71 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -4,7 +4,7 @@
 # TODO: LWP/Pg perl libs aren't recognized
 
 # TODO: support fastcgi
-# http://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
+# https://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
 # spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
 # spawn-fcgi -s /run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
 # https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
@@ -83,42 +83,47 @@ let
   # Copy one Munin plugin into the Nix store with a specific name.
   # This is suitable for use with plugins going directly into /etc/munin/plugins,
   # i.e. munin.extraPlugins.
-  internOnePlugin = name: path:
+  internOnePlugin = { name, path }:
     "cp -a '${path}' '${name}'";
 
   # Copy an entire tree of Munin plugins into a single directory in the Nix
-  # store, with no renaming.
-  # This is suitable for use with munin-node-configure --suggest, i.e.
-  # munin.extraAutoPlugins.
-  internManyPlugins = name: path:
+  # store, with no renaming. The output is suitable for use with
+  # munin-node-configure --suggest, i.e. munin.extraAutoPlugins.
+  # Note that this flattens the input; this is intentional, as
+  # munin-node-configure won't recurse into subdirectories.
+  internManyPlugins = path:
     "find '${path}' -type f -perm /a+x -exec cp -a -t . '{}' '+'";
 
   # Use the appropriate intern-fn to copy the plugins into the store and patch
   # them afterwards in an attempt to get them to run on NixOS.
+  # This is a bit hairy because we can't just fix shebangs; lots of munin plugins
+  # hardcode paths like /sbin/mount rather than trusting $PATH, so we have to
+  # look for and update those throughout the script. At the same time, if the
+  # plugin comes from a package that is already nixified, we don't want to
+  # rewrite paths like /nix/store/foo/sbin/mount.
+  # For now we make the simplifying assumption that no file will contain lines
+  # which mix store paths and FHS paths, and thus run our substitution only on
+  # lines which do not contain store paths.
   internAndFixPlugins = name: intern-fn: paths:
     pkgs.runCommand name {} ''
       mkdir -p "$out"
       cd "$out"
-      ${lib.concatStringsSep "\n"
-          (lib.attrsets.mapAttrsToList intern-fn paths)}
+      ${lib.concatStringsSep "\n" (map intern-fn paths)}
       chmod -R u+w .
-      find . -type f -exec sed -E -i '
-        s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
-      ' '{}' '+'
+      ${pkgs.findutils}/bin/find . -type f -exec ${pkgs.gnused}/bin/sed -E -i "
+        \%''${NIX_STORE}/%! s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
+      " '{}' '+'
     '';
 
   # TODO: write a derivation for munin-contrib, so that for contrib plugins
   # you can just refer to them by name rather than needing to include a copy
   # of munin-contrib in your nixos configuration.
   extraPluginDir = internAndFixPlugins "munin-extra-plugins.d"
-    internOnePlugin nodeCfg.extraPlugins;
+    internOnePlugin
+    (lib.attrsets.mapAttrsToList (k: v: { name = k; path = v; }) nodeCfg.extraPlugins);
 
   extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d"
-    internManyPlugins
-    (builtins.listToAttrs
-      (map
-        (path: { name = baseNameOf path; value = path; })
-        nodeCfg.extraAutoPlugins));
+    internManyPlugins nodeCfg.extraAutoPlugins;
 
   customStaticDir = pkgs.runCommand "munin-custom-static-data" {} ''
     cp -a "${pkgs.munin}/etc/opt/munin/static" "$out"
@@ -142,7 +147,7 @@ in
           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 <http://guide.munin-monitoring.org/en/latest/architecture/index.html>.
+          See <https://guide.munin-monitoring.org/en/latest/architecture/index.html>.
         '';
       };
 
@@ -151,7 +156,7 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`munin-node.conf` extra configuration. See
-          <http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
+          <https://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html>
         '';
       };
 
@@ -160,7 +165,7 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`plugin-conf.d` extra plugin configuration. See
-          <http://guide.munin-monitoring.org/en/latest/plugin/use.html>
+          <https://guide.munin-monitoring.org/en/latest/plugin/use.html>
         '';
         example = ''
           [fail2ban_*]
@@ -268,9 +273,9 @@ in
         type = types.lines;
         description = lib.mdDoc ''
           {file}`munin.conf` extra global configuration.
-          See <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
+          See <https://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>.
           Useful to setup notifications, see
-          <http://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
+          <https://guide.munin-monitoring.org/en/latest/tutorial/alert.html>
         '';
         example = ''
           contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
@@ -283,7 +288,7 @@ in
         description = lib.mdDoc ''
           Definitions of hosts of nodes to collect data from. Needs at least one
           host for cron to succeed. See
-          <http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
+          <https://guide.munin-monitoring.org/en/latest/reference/munin.conf.html>
         '';
         example = literalExpression ''
           '''
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 8feff22c11826..dc5fa1be29228 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -88,7 +88,7 @@ in
 
   options = {
     services.nagios = {
-      enable = mkEnableOption (lib.mdDoc ''[Nagios](http://www.nagios.org/) to monitor your system or network.'');
+      enable = mkEnableOption (lib.mdDoc ''[Nagios](https://www.nagios.org/) to monitor your system or network.'');
 
       objectDefs = mkOption {
         description = lib.mdDoc ''
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 3833418b5addd..5cf3c096397cb 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -12,6 +12,7 @@ let
     ln -s /run/wrappers/bin/perf.plugin $out/libexec/netdata/plugins.d/perf.plugin
     ln -s /run/wrappers/bin/slabinfo.plugin $out/libexec/netdata/plugins.d/slabinfo.plugin
     ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin
+    ln -s /run/wrappers/bin/systemd-journal.plugin $out/libexec/netdata/plugins.d/systemd-journal.plugin
   '';
 
   plugins = [
@@ -51,12 +52,7 @@ in {
     services.netdata = {
       enable = mkEnableOption (lib.mdDoc "netdata");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.netdata;
-        defaultText = literalExpression "pkgs.netdata";
-        description = lib.mdDoc "Netdata package to use.";
-      };
+      package = mkPackageOption pkgs "netdata" { };
 
       user = mkOption {
         type = types.str;
@@ -202,6 +198,7 @@ in {
         }
       ];
 
+    services.netdata.configDir.".opt-out-from-anonymous-statistics" = mkIf (!cfg.enableAnalyticsReporting) (pkgs.writeText ".opt-out-from-anonymous-statistics" "");
     environment.etc."netdata/netdata.conf".source = configFile;
     environment.etc."netdata/conf.d".source = configDirectory;
 
@@ -209,7 +206,15 @@ in {
       description = "Real time performance monitoring";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = (with pkgs; [ curl gawk iproute2 which procps bash ])
+      path = (with pkgs; [
+          curl
+          gawk
+          iproute2
+          which
+          procps
+          bash
+          util-linux # provides logger command; required for syslog health alarms
+      ])
         ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages)
         ++ lib.optional config.virtualisation.libvirtd.enable (config.virtualisation.libvirtd.package);
       environment = {
@@ -254,7 +259,7 @@ in {
         # Capabilities
         CapabilityBoundingSet = [
           "CAP_DAC_OVERRIDE"      # is required for freeipmi and slabinfo plugins
-          "CAP_DAC_READ_SEARCH"   # is required for apps plugin
+          "CAP_DAC_READ_SEARCH"   # is required for apps and systemd-journal plugin
           "CAP_FOWNER"            # is required for freeipmi plugin
           "CAP_SETPCAP"           # is required for apps, perf and slabinfo plugins
           "CAP_SYS_ADMIN"         # is required for perf plugin
@@ -263,6 +268,7 @@ in {
           "CAP_NET_RAW"           # is required for fping app
           "CAP_SYS_CHROOT"        # is required for cgroups plugin
           "CAP_SETUID"            # is required for cgroups and cgroups-network plugins
+          "CAP_SYSLOG"            # is required for systemd-journal plugin
         ];
         # Sandboxing
         ProtectSystem = "full";
@@ -318,6 +324,14 @@ in {
         permissions = "u+rx,g+x,o-rwx";
       };
 
+      "systemd-journal.plugin" = {
+        source = "${cfg.package}/libexec/netdata/plugins.d/systemd-journal.plugin.org";
+        capabilities = "cap_dac_read_search,cap_syslog+ep";
+        owner = cfg.user;
+        group = cfg.group;
+        permissions = "u+rx,g+x,o-rwx";
+      };
+
       "slabinfo.plugin" = {
         source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org";
         capabilities = "cap_dac_override+ep";
diff --git a/nixos/modules/services/monitoring/ocsinventory-agent.md b/nixos/modules/services/monitoring/ocsinventory-agent.md
new file mode 100644
index 0000000000000..50e246fb6531b
--- /dev/null
+++ b/nixos/modules/services/monitoring/ocsinventory-agent.md
@@ -0,0 +1,33 @@
+# OCS Inventory Agent {#module-services-ocsinventory-agent}
+
+[OCS Inventory NG](https://ocsinventory-ng.org/) or Open Computers and Software inventory
+is an application designed to help IT administrator to keep track of the hardware and software
+configurations of computers that are installed on their network.
+
+OCS Inventory collects information about the hardware and software of networked machines
+through the **OCS Inventory Agent** program.
+
+This NixOS module enables you to install and configure this agent so that it sends information from your computer to the OCS Inventory server.
+
+For more technical information about OCS Inventory Agent, refer to [the Wiki documentation](https://wiki.ocsinventory-ng.org/03.Basic-documentation/Setting-up-the-UNIX-agent-manually-on-client-computers/).
+
+
+## Basic Usage {#module-services-ocsinventory-agent-basic-usage}
+
+A minimal configuration looks like this:
+
+```nix
+{
+  services.ocsinventory-agent = {
+    enable = true;
+    settings = {
+      server = "https://ocsinventory.localhost:8080/ocsinventory";
+      tag = "01234567890123";
+    };
+  };
+}
+```
+
+This configuration will periodically run the ocsinventory-agent SystemD service.
+
+The OCS Inventory Agent will inventory the computer and then sends the results to the specified OCS Inventory Server.
diff --git a/nixos/modules/services/monitoring/ocsinventory-agent.nix b/nixos/modules/services/monitoring/ocsinventory-agent.nix
new file mode 100644
index 0000000000000..a36375587759c
--- /dev/null
+++ b/nixos/modules/services/monitoring/ocsinventory-agent.nix
@@ -0,0 +1,134 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.ocsinventory-agent;
+
+  settingsFormat = pkgs.formats.keyValue {
+    mkKeyValue = lib.generators.mkKeyValueDefault { } "=";
+  };
+
+in
+{
+  meta = {
+    doc = ./ocsinventory-agent.md;
+    maintainers = with lib.maintainers; [ anthonyroussel ];
+  };
+
+  options = {
+    services.ocsinventory-agent = {
+      enable = lib.mkEnableOption (lib.mdDoc "OCS Inventory Agent");
+
+      package = lib.mkPackageOption pkgs "ocsinventory-agent" { };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type.nestedTypes.elemType;
+
+          options = {
+            server = lib.mkOption {
+              type = lib.types.nullOr lib.types.str;
+              example = "https://ocsinventory.localhost:8080/ocsinventory";
+              default = null;
+              description = lib.mdDoc ''
+                The URI of the OCS Inventory server where to send the inventory file.
+
+                This option is ignored if {option}`services.ocsinventory-agent.settings.local` is set.
+              '';
+            };
+
+            local = lib.mkOption {
+              type = lib.types.nullOr lib.types.path;
+              example = "/var/lib/ocsinventory-agent/reports";
+              default = null;
+              description = lib.mdDoc ''
+                If specified, the OCS Inventory Agent will run in offline mode
+                and the resulting inventory file will be stored in the specified path.
+              '';
+            };
+
+            ca = lib.mkOption {
+              type = lib.types.path;
+              default = "/etc/ssl/certs/ca-certificates.crt";
+              description = lib.mdDoc ''
+                Path to CA certificates file in PEM format, for server
+                SSL certificate validation.
+              '';
+            };
+
+            tag = lib.mkOption {
+              type = lib.types.nullOr lib.types.str;
+              default = null;
+              example = "01234567890123";
+              description = lib.mdDoc "Tag for the generated inventory.";
+            };
+
+            debug = lib.mkEnableOption (lib.mdDoc "debug mode");
+          };
+        };
+        default = { };
+        example = {
+          ca = "/etc/ssl/certs/ca-certificates.crt";
+          debug = true;
+          server = "https://ocsinventory.localhost:8080/ocsinventory";
+          tag = "01234567890123";
+        };
+        description = lib.mdDoc ''
+          Configuration for /etc/ocsinventory-agent/ocsinventory-agent.cfg.
+
+          Refer to
+          {manpage}`ocsinventory-agent(1)` for available options.
+        '';
+      };
+
+      interval = lib.mkOption {
+        type = lib.types.str;
+        default = "daily";
+        example = "06:00";
+        description = lib.mdDoc ''
+          How often we run the ocsinventory-agent service. Runs by default every daily.
+
+          The format is described in
+          {manpage}`systemd.time(7)`.
+        '';
+      };
+    };
+  };
+
+  config =
+    let
+      configFile = settingsFormat.generate "ocsinventory-agent.cfg" cfg.settings;
+
+    in lib.mkIf cfg.enable {
+      # Path of the configuration file is hard-coded and cannot be changed
+      # https://github.com/OCSInventory-NG/UnixAgent/blob/v2.10.0/lib/Ocsinventory/Agent/Config.pm#L78
+      #
+      environment.etc."ocsinventory-agent/ocsinventory-agent.cfg".source = configFile;
+
+      systemd.services.ocsinventory-agent = {
+        description = "OCS Inventory Agent service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        reloadTriggers = [ configFile ];
+
+        serviceConfig = {
+          ExecStart = lib.getExe cfg.package;
+          ConfigurationDirectory = "ocsinventory-agent";
+          StateDirectory = "ocsinventory-agent";
+        };
+      };
+
+      systemd.timers.ocsinventory-agent = {
+        description = "Launch OCS Inventory Agent regularly";
+        wantedBy = [ "timers.target" ];
+
+        timerConfig = {
+          OnCalendar = cfg.interval;
+          AccuracySec = "1h";
+          RandomizedDelaySec = 240;
+          Persistent = true;
+          Unit = "ocsinventory-agent.service";
+        };
+      };
+    };
+}
diff --git a/nixos/modules/services/monitoring/opentelemetry-collector.nix b/nixos/modules/services/monitoring/opentelemetry-collector.nix
index 1d211b6897772..83ad550dcdf36 100644
--- a/nixos/modules/services/monitoring/opentelemetry-collector.nix
+++ b/nixos/modules/services/monitoring/opentelemetry-collector.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkEnableOption mkIf mkOption types getExe;
+  inherit (lib) mkEnableOption mkPackageOption mkIf mkOption types getExe;
 
   cfg = config.services.opentelemetry-collector;
   opentelemetry-collector = cfg.package;
@@ -11,12 +11,7 @@ in {
   options.services.opentelemetry-collector = {
     enable = mkEnableOption (lib.mdDoc "Opentelemetry Collector");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.opentelemetry-collector;
-      defaultText = lib.literalExpression "pkgs.opentelemetry-collector";
-      description = lib.mdDoc "The opentelemetry-collector package to use.";
-    };
+    package = mkPackageOption pkgs "opentelemetry-collector" { };
 
     settings = mkOption {
       type = settingsFormat.type;
diff --git a/nixos/modules/services/monitoring/parsedmarc.nix b/nixos/modules/services/monitoring/parsedmarc.nix
index 44fc359b6a7de..a146e7ab95435 100644
--- a/nixos/modules/services/monitoring/parsedmarc.nix
+++ b/nixos/modules/services/monitoring/parsedmarc.nix
@@ -301,6 +301,7 @@ in
               description = lib.mdDoc ''
                 The addresses to send outgoing mail to.
               '';
+              apply = x: if x == [] then null else lib.concatStringsSep "," x;
             };
           };
 
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix b/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
index b81d5f6db5e08..9b9bafa09441b 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager-irc-relay.nix
@@ -12,12 +12,7 @@ in
   options.services.prometheus.alertmanagerIrcRelay = {
     enable = mkEnableOption (mdDoc "Alertmanager IRC Relay");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.alertmanager-irc-relay;
-      defaultText = literalExpression "pkgs.alertmanager-irc-relay";
-      description = mdDoc "Alertmanager IRC Relay package to use.";
-    };
+    package = mkPackageOption pkgs "alertmanager-irc-relay" { };
 
     extraFlags = mkOption {
       type = types.listOf types.str;
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 987f17c2c6e68..bb426d8b7beb0 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -8,7 +8,7 @@ let
 
   checkedConfig = file:
     if cfg.checkConfig then
-      pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
+      pkgs.runCommand "checked-config" { nativeBuildInputs = [ cfg.package ]; } ''
         ln -s ${file} $out
         amtool check-config $out
       '' else file;
@@ -44,14 +44,7 @@ in {
     services.prometheus.alertmanager = {
       enable = mkEnableOption (lib.mdDoc "Prometheus Alertmanager");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.prometheus-alertmanager;
-        defaultText = literalExpression "pkgs.alertmanager";
-        description = lib.mdDoc ''
-          Package that should be used for alertmanager.
-        '';
-      };
+      package = mkPackageOption pkgs "prometheus-alertmanager" { };
 
       configuration = mkOption {
         type = types.nullOr types.attrs;
@@ -181,6 +174,7 @@ in {
 
       systemd.services.alertmanager = {
         wantedBy = [ "multi-user.target" ];
+        wants    = [ "network-online.target" ];
         after    = [ "network-online.target" ];
         preStart = ''
            ${lib.getBin pkgs.envsubst}/bin/envsubst -o "/tmp/alert-manager-substituted.yaml" \
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 19ee3ae6f7da8..b4ac8e21451af 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -31,7 +31,7 @@ let
     if checkConfigEnabled then
       pkgs.runCommandLocal
         "${name}-${replaceStrings [" "] [""] what}-checked"
-        { buildInputs = [ cfg.package.cli ]; } ''
+        { nativeBuildInputs = [ cfg.package.cli ]; } ''
         ln -s ${file} $out
         promtool ${what} $out
       '' else file;
@@ -41,12 +41,12 @@ let
   # This becomes the main config file for Prometheus
   promConfig = {
     global = filterValidPrometheus cfg.globalConfig;
-    rule_files = map (promtoolCheck "check rules" "rules") (cfg.ruleFiles ++ [
-      (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg.rules))
-    ]);
     scrape_configs = filterValidPrometheus cfg.scrapeConfigs;
     remote_write = filterValidPrometheus cfg.remoteWrite;
     remote_read = filterValidPrometheus cfg.remoteRead;
+    rule_files = optionals (!(cfg.enableAgentMode)) (map (promtoolCheck "check rules" "rules") (cfg.ruleFiles ++ [
+      (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg.rules))
+    ]));
     alerting = {
       inherit (cfg) alertmanagers;
     };
@@ -62,15 +62,20 @@ let
     promtoolCheck "check config ${lib.optionalString (cfg.checkConfig == "syntax-only") "--syntax-only"}" "prometheus.yml" yml;
 
   cmdlineArgs = cfg.extraFlags ++ [
-    "--storage.tsdb.path=${workingDir}/data/"
     "--config.file=${
       if cfg.enableReload
       then "/etc/prometheus/prometheus.yaml"
       else prometheusYml
     }"
     "--web.listen-address=${cfg.listenAddress}:${builtins.toString cfg.port}"
-    "--alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
-  ] ++ optional (cfg.webExternalUrl != null) "--web.external-url=${cfg.webExternalUrl}"
+  ] ++ (
+    if (cfg.enableAgentMode) then [
+      "--enable-feature=agent"
+    ] else [
+       "--alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity }"
+       "--storage.tsdb.path=${workingDir}/data/"
+    ])
+    ++ optional (cfg.webExternalUrl != null) "--web.external-url=${cfg.webExternalUrl}"
     ++ optional (cfg.retentionTime != null) "--storage.tsdb.retention.time=${cfg.retentionTime}"
     ++ optional (cfg.webConfigFile != null) "--web.config.file=${cfg.webConfigFile}";
 
@@ -1430,6 +1435,10 @@ let
       remote_timeout = mkOpt types.str ''
         Timeout for requests to the remote write endpoint.
       '';
+      headers = mkOpt (types.attrsOf types.str) ''
+        Custom HTTP headers to be sent along with each remote write request.
+        Be aware that headers that are set by Prometheus itself can't be overwritten.
+      '';
       write_relabel_configs = mkOpt (types.listOf promTypes.relabel_config) ''
         List of remote write relabel configurations.
       '';
@@ -1525,6 +1534,10 @@ let
       remote_timeout = mkOpt types.str ''
         Timeout for requests to the remote read endpoint.
       '';
+      headers = mkOpt (types.attrsOf types.str) ''
+        Custom HTTP headers to be sent along with each remote read request.
+        Be aware that headers that are set by Prometheus itself can't be overwritten.
+      '';
       read_recent = mkOpt types.bool ''
         Whether reads should be made for queries for time ranges that
         the local storage should have complete data for.
@@ -1564,14 +1577,7 @@ in
 
     enable = mkEnableOption (lib.mdDoc "Prometheus monitoring daemon");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.prometheus;
-      defaultText = literalExpression "pkgs.prometheus";
-      description = lib.mdDoc ''
-        The prometheus package that should be used.
-      '';
-    };
+    package = mkPackageOption pkgs "prometheus" { };
 
     port = mkOption {
       type = types.port;
@@ -1619,6 +1625,8 @@ in
       '';
     };
 
+    enableAgentMode = mkEnableOption (lib.mdDoc "agent mode");
+
     configText = mkOption {
       type = types.nullOr types.lines;
       default = null;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 8bb017894ee2d..35db8a7376b11 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -2,8 +2,8 @@
 
 let
   inherit (lib) concatStrings foldl foldl' genAttrs literalExpression maintainers
-                mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
-                optional types mkOptionDefault flip attrNames;
+    mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
+    optional types mkOptionDefault flip attrNames;
 
   cfg = config.services.prometheus.exporters;
 
@@ -20,7 +20,7 @@ let
   #  systemd service must be provided by specifying either
   #  `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
 
-  exporterOpts = genAttrs [
+  exporterOpts = (genAttrs [
     "apcupsd"
     "artifactory"
     "bind"
@@ -34,13 +34,15 @@ let
     "domain"
     "dovecot"
     "fastly"
+    "flow"
     "fritzbox"
     "graphite"
     "idrac"
+    "imap-mailstat"
     "influxdb"
     "ipmi"
-    "json"
     "jitsi"
+    "json"
     "junos-czerwonk"
     "kea"
     "keylight"
@@ -50,6 +52,7 @@ let
     "mikrotik"
     "minio"
     "modemmanager"
+    "mongodb"
     "mysqld"
     "nextcloud"
     "nginx"
@@ -58,8 +61,10 @@ let
     "nut"
     "openldap"
     "openvpn"
+    "pgbouncer"
     "php-fpm"
     "pihole"
+    "ping"
     "postfix"
     "postgres"
     "process"
@@ -68,12 +73,13 @@ let
     "redis"
     "rspamd"
     "rtl_433"
+    "sabnzbd"
     "scaphandre"
     "script"
     "shelly"
-    "snmp"
     "smartctl"
     "smokeping"
+    "snmp"
     "sql"
     "statsd"
     "surfboard"
@@ -85,10 +91,39 @@ let
     "v2ray"
     "varnish"
     "wireguard"
-    "flow"
     "zfs"
-  ] (name:
-    import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+  ]
+    (name:
+      import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+    )) // (mapAttrs
+    (name: params:
+      import (./. + "/exporters/${params.name}.nix") { inherit config lib pkgs options; type = params.type ; })
+    {
+      exportarr-bazarr = {
+        name = "exportarr";
+        type = "bazarr";
+      };
+      exportarr-lidarr = {
+        name = "exportarr";
+        type = "lidarr";
+      };
+      exportarr-prowlarr = {
+        name = "exportarr";
+        type = "prowlarr";
+      };
+      exportarr-radarr = {
+        name = "exportarr";
+        type = "radarr";
+      };
+      exportarr-readarr = {
+        name = "exportarr";
+        type = "readarr";
+      };
+      exportarr-sonarr = {
+        name = "exportarr";
+        type = "sonarr";
+      };
+    }
   );
 
   mkExporterOpts = ({ name, port }: {
@@ -304,6 +339,33 @@ in
           'services.mysql.enable' is set to false.
       '';
     } {
+      assertion = cfg.nextcloud.enable -> (
+        (cfg.nextcloud.passwordFile == null) != (cfg.nextcloud.tokenFile == null)
+      );
+      message = ''
+        Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or
+          'services.prometheus.exporters.nextcloud.tokenFile'
+      '';
+    } {
+      assertion =  cfg.pgbouncer.enable -> (
+        (cfg.pgbouncer.connectionStringFile != null || cfg.pgbouncer.connectionString != "")
+      );
+        message = ''
+          PgBouncer exporter needs either connectionStringFile or connectionString configured"
+        '';
+    } {
+      assertion = cfg.pgbouncer.enable -> (
+        config.services.pgbouncer.ignoreStartupParameters != null && builtins.match ".*extra_float_digits.*" config.services.pgbouncer.ignoreStartupParameters != null
+        );
+        message = ''
+          Prometheus PgBouncer exporter requires including `extra_float_digits` in services.pgbouncer.ignoreStartupParameters
+
+          Example:
+          services.pgbouncer.ignoreStartupParameters = extra_float_digits;
+
+          See https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration
+        '';
+    } {
       assertion = cfg.sql.enable -> (
         (cfg.sql.configFile == null) != (cfg.sql.configuration == null)
       );
@@ -341,12 +403,24 @@ in
         `openFirewall' is set to `true'!
       '';
     })) ++ config.services.prometheus.exporters.assertions;
-    warnings = [(mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) ''
-        Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override
-        `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`.
-        Consider using `services.prometheus.exporters.idrac.configuration` instead.
-      ''
-    )] ++ config.services.prometheus.exporters.warnings;
+    warnings = [
+      (mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) ''
+          Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override
+          `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`.
+          Consider using `services.prometheus.exporters.idrac.configuration` instead.
+        ''
+      )
+      (mkIf
+        (cfg.pgbouncer.enable && cfg.pgbouncer.connectionString != "") ''
+          config.services.prometheus.exporters.pgbouncer.connectionString is insecure. Use connectionStringFile instead.
+        ''
+      )
+      (mkIf
+        (cfg.pgbouncer.enable && config.services.pgbouncer.authType != "any") ''
+          Admin user (with password or passwordless) MUST exist in the services.pgbouncer.authFile if authType other than any is used.
+        ''
+      )
+    ] ++ 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/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
index 66eaed51d2ead..ce2c391de5232 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -21,11 +21,11 @@ let
       throw
       "${logPrefix}: configuration file must not reside within /tmp - it won't be visible to the systemd service."
     else
-      true;
+      file;
   checkConfig = file:
     pkgs.runCommand "checked-blackbox-exporter.conf" {
       preferLocalBuild = true;
-      buildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ];
+      nativeBuildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ];
     } ''
       ln -s ${coerceConfigFile file} $out
       blackbox_exporter --config.check --config.file $out
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix b/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix
new file mode 100644
index 0000000000000..8511abbee1bd0
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/exportarr.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, options, type }:
+
+let
+  cfg = config.services.prometheus.exporters."exportarr-${type}";
+  exportarrEnvironment = (
+    lib.mapAttrs (_: toString) cfg.environment
+  ) // {
+    PORT = toString cfg.port;
+    URL = cfg.url;
+    API_KEY_FILE = lib.mkIf (cfg.apiKeyFile != null) "%d/api-key";
+  };
+in
+{
+  port = 9708;
+  extraOpts = {
+    url = lib.mkOption {
+      type = lib.types.str;
+      default = "http://127.0.0.1";
+      description = lib.mdDoc ''
+        The full URL to Sonarr, Radarr, or Lidarr.
+      '';
+    };
+
+    apiKeyFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = lib.mdDoc ''
+        File containing the api-key.
+      '';
+    };
+
+    package = lib.mkPackageOption pkgs "exportarr" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      default = { };
+      description = lib.mdDoc ''
+        See [the configuration guide](https://github.com/onedr0p/exportarr#configuration) for available options.
+      '';
+      example = {
+        PROWLARR__BACKFILL = true;
+      };
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      LoadCredential = lib.optionalString (cfg.apiKeyFile != null) "api-key:${cfg.apiKeyFile}";
+      ExecStart = ''${cfg.package}/bin/exportarr ${type} "$@"'';
+      ProcSubset = "pid";
+      ProtectProc = "invisible";
+      SystemCallFilter = ["@system-service" "~@privileged"];
+    };
+    environment = exportarrEnvironment;
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/imap-mailstat.nix b/nixos/modules/services/monitoring/prometheus/exporters/imap-mailstat.nix
new file mode 100644
index 0000000000000..c5024a258e719
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/imap-mailstat.nix
@@ -0,0 +1,71 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.imap-mailstat;
+  valueToString = value:
+    if (builtins.typeOf value == "string") then "\"${value}\""
+    else (
+      if (builtins.typeOf value == "int") then "${toString value}"
+      else (
+        if (builtins.typeOf value == "bool") then (if value then "true" else "false")
+        else "XXX ${toString value}"
+      )
+    );
+  createConfigFile = accounts:
+    # unfortunately on toTOML yet
+    # https://github.com/NixOS/nix/issues/3929
+    pkgs.writeText "imap-mailstat-exporter.conf" ''
+      ${concatStrings (attrValues (mapAttrs (name: config: "[[Accounts]]\nname = \"${name}\"\n${concatStrings (attrValues (mapAttrs (k: v: "${k} = ${valueToString v}\n") config))}") accounts))}
+    '';
+  mkOpt = type: description: mkOption {
+    type = types.nullOr type;
+    default = null;
+    description = lib.mdDoc description;
+  };
+  accountOptions.options = {
+    mailaddress = mkOpt types.str "Your email address (at the moment used as login name)";
+    username = mkOpt types.str "If empty string mailaddress value is used";
+    password = mkOpt types.str "";
+    serveraddress = mkOpt types.str "mailserver name or address";
+    serverport = mkOpt types.int "imap port number (at the moment only tls connection is supported)";
+    starttls = mkOpt types.bool "set to true for using STARTTLS to start a TLS connection";
+  };
+in
+{
+  port = 8081;
+  extraOpts = {
+    oldestUnseenDate = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable metric with timestamp of oldest unseen mail
+      '';
+    };
+    accounts = mkOption {
+      type = types.attrsOf (types.submodule accountOptions);
+      default = {};
+      description = lib.mdDoc ''
+        Accounts to monitor
+      '';
+    };
+    configurationFile = mkOption {
+      type = types.path;
+      example = "/path/to/config-file";
+      description = lib.mdDoc ''
+        File containing the configuration
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-imap-mailstat-exporter}/bin/imap-mailstat-exporter \
+          -config ${createConfigFile cfg.accounts} \
+          ${optionalString cfg.oldestUnseenDate "-oldestunseendate"} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
index ed33c72f644f3..3abb6ff6bdf8b 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/kea.nix
@@ -31,13 +31,15 @@ in {
     ];
     serviceConfig = {
       User = "kea";
+      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-kea-exporter}/bin/kea-exporter \
           --address ${cfg.listenAddress} \
           --port ${toString cfg.port} \
           ${concatStringsSep " " cfg.controlSocketPaths}
       '';
-      SupplementaryGroups = [ "kea" ];
+      RuntimeDirectory = "kea";
+      RuntimeDirectoryPreserve = true;
       RestrictAddressFamilies = [
         # Need AF_UNIX to collect data
         "AF_UNIX"
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/knot.nix b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
index a73425b37da71..7758487508033 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/knot.nix
@@ -8,9 +8,9 @@ in {
   port = 9433;
   extraOpts = {
     knotLibraryPath = mkOption {
-      type = types.str;
-      default = "${pkgs.knot-dns.out}/lib/libknot.so";
-      defaultText = literalExpression ''"''${pkgs.knot-dns.out}/lib/libknot.so"'';
+      type = types.nullOr types.str;
+      default = null;
+      example = literalExpression ''"''${pkgs.knot-dns.out}/lib/libknot.so"'';
       description = lib.mdDoc ''
         Path to the library of `knot-dns`.
       '';
@@ -25,7 +25,7 @@ in {
     };
 
     knotSocketTimeout = mkOption {
-      type = types.int;
+      type = types.ints.positive;
       default = 2000;
       description = lib.mdDoc ''
         Timeout in seconds.
@@ -33,17 +33,22 @@ in {
     };
   };
   serviceOpts = {
+    path = with pkgs; [
+      procps
+    ];
     serviceConfig = {
       ExecStart = ''
-        ${pkgs.prometheus-knot-exporter}/bin/knot_exporter \
+        ${pkgs.prometheus-knot-exporter}/bin/knot-exporter \
           --web-listen-addr ${cfg.listenAddress} \
           --web-listen-port ${toString cfg.port} \
-          --knot-library-path ${cfg.knotLibraryPath} \
           --knot-socket-path ${cfg.knotSocketPath} \
           --knot-socket-timeout ${toString cfg.knotSocketTimeout} \
+          ${lib.optionalString (cfg.knotLibraryPath != null) "--knot-library-path ${cfg.knotLibraryPath}"} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
-      SupplementaryGroups = [ "knot" ];
+      SupplementaryGroups = [
+        "knot"
+      ];
       RestrictAddressFamilies = [
         # Need AF_UNIX to collect data
         "AF_UNIX"
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mongodb.nix b/nixos/modules/services/monitoring/prometheus/exporters/mongodb.nix
new file mode 100644
index 0000000000000..b36a09c609206
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/mongodb.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.mongodb;
+in
+{
+  port = 9216;
+  extraOpts = {
+    uri = mkOption {
+      type = types.str;
+      default = "mongodb://localhost:27017/test";
+      example = "mongodb://localhost:27017/test";
+      description = lib.mdDoc "MongoDB URI to connect to.";
+    };
+    collStats = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "db1.coll1" "db2" ];
+      description = lib.mdDoc ''
+        List of comma separared databases.collections to get $collStats
+      '';
+    };
+    indexStats = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "db1.coll1" "db2" ];
+      description = lib.mdDoc ''
+        List of comma separared databases.collections to get $indexStats
+      '';
+    };
+    collector = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "diagnosticdata" "replicasetstatus" "dbstats" "topmetrics" "currentopmetrics" "indexstats" "dbstats" "profile" ];
+      description = lib.mdDoc "Enabled collectors";
+    };
+    collectAll = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Enable all collectors. Same as specifying all --collector.<name>
+      '';
+    };
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      example = "/metrics";
+      description = lib.mdDoc "Metrics expose path";
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      RuntimeDirectory = "prometheus-mongodb-exporter";
+      ExecStart = ''
+        ${getExe pkgs.prometheus-mongodb-exporter} \
+          --mongodb.uri="${cfg.uri}" \
+          ${if cfg.collectAll then "--collect-all" else concatMapStringsSep " " (x: "--collect.${x}") cfg.collector} \
+          ${optionalString (length cfg.collStats > 0) "--mongodb.collstats-colls=${concatStringsSep "," cfg.collStats}"} \
+          ${optionalString (length cfg.indexStats > 0) "--mongodb.indexstats-colls=${concatStringsSep "," cfg.indexStats}"} \
+          --web.listen-address="${cfg.listenAddress}:${toString cfg.port}" \
+          --web.telemetry-path="${cfg.telemetryPath}" \
+          ${escapeShellArgs cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
index 28add020f5cc3..28a3eb6a134c0 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
@@ -23,10 +23,12 @@ in
       description = lib.mdDoc ''
         Username for connecting to Nextcloud.
         Note that this account needs to have admin privileges in Nextcloud.
+        Unused when using token authentication.
       '';
     };
     passwordFile = mkOption {
-      type = types.path;
+      type = types.nullOr types.path;
+      default = null;
       example = "/path/to/password-file";
       description = lib.mdDoc ''
         File containing the password for connecting to Nextcloud.
@@ -34,9 +36,9 @@ in
       '';
     };
     tokenFile = mkOption {
-      type = types.path;
+      type = types.nullOr types.path;
+      default = null;
       example = "/path/to/token-file";
-      default = "";
       description = lib.mdDoc ''
         File containing the token for connecting to Nextcloud.
         Make sure that this file is readable by the exporter user.
@@ -58,12 +60,13 @@ in
           --addr ${cfg.listenAddress}:${toString cfg.port} \
           --timeout ${cfg.timeout} \
           --server ${cfg.url} \
-          ${if cfg.tokenFile == "" then ''
+          ${if cfg.passwordFile != null then ''
             --username ${cfg.username} \
             --password ${escapeShellArg "@${cfg.passwordFile}"} \
-         '' else ''
+          '' else ''
             --auth-token ${escapeShellArg "@${cfg.tokenFile}"} \
-         ''} ${concatStringsSep " \\\n  " cfg.extraFlags}'';
+          ''} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}'';
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
index 3158e71f0468b..88dc79fc2503f 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -43,14 +43,14 @@ in
     };
   };
   serviceOpts = mkMerge ([{
+    environment.CONST_LABELS = concatStringsSep "," cfg.constLabels;
     serviceConfig = {
       ExecStart = ''
         ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \
           --nginx.scrape-uri='${cfg.scrapeUri}' \
-          --nginx.ssl-verify=${boolToString cfg.sslVerify} \
+          --${lib.optionalString (!cfg.sslVerify) "no-"}nginx.ssl-verify \
           --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
           --web.telemetry-path=${cfg.telemetryPath} \
-          --prometheus.const-labels=${concatStringsSep "," cfg.constLabels} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix b/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix
new file mode 100644
index 0000000000000..9e55cadae5237
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/pgbouncer.nix
@@ -0,0 +1,145 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.pgbouncer;
+in
+{
+  port = 9127;
+  extraOpts = {
+
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = lib.mdDoc ''
+        Path under which to expose metrics.
+      '';
+    };
+
+    connectionString = mkOption {
+      type = types.str;
+      default = "";
+      example = "postgres://admin:@localhost:6432/pgbouncer?sslmode=require";
+      description = lib.mdDoc ''
+        Connection string for accessing pgBouncer.
+
+        NOTE: You MUST keep pgbouncer as database name (special internal db)!!!
+
+        NOTE: Admin user (with password or passwordless) MUST exist
+        in the services.pgbouncer.authFile if authType other than any is used.
+
+        WARNING: this secret is stored in the world-readable Nix store!
+        Use {option}`connectionStringFile` instead.
+      '';
+    };
+
+    connectionStringFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/run/keys/pgBouncer-connection-string";
+      description = lib.mdDoc ''
+        File that contains pgBouncer connection string in format:
+        postgres://admin:@localhost:6432/pgbouncer?sslmode=require
+
+        NOTE: You MUST keep pgbouncer as database name (special internal db)!!!
+
+        NOTE: Admin user (with password or passwordless) MUST exist
+        in the services.pgbouncer.authFile if authType other than any is used.
+
+        {option}`connectionStringFile` takes precedence over {option}`connectionString`
+      '';
+    };
+
+    pidFile = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = lib.mdDoc ''
+        Path to PgBouncer pid file.
+
+        If provided, the standard process metrics get exported for the PgBouncer
+        process, prefixed with 'pgbouncer_process_...'. The pgbouncer_process exporter
+        needs to have read access to files owned by the PgBouncer process. Depends on
+        the availability of /proc.
+
+        https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics.
+
+      '';
+    };
+
+    webSystemdSocket = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Use systemd socket activation listeners instead of port listeners (Linux only).
+      '';
+    };
+
+    logLevel = mkOption {
+      type = types.enum ["debug" "info" "warn" "error" ];
+      default = "info";
+      description = lib.mdDoc ''
+        Only log messages with the given severity or above.
+      '';
+    };
+
+    logFormat = mkOption {
+      type = types.enum ["logfmt" "json"];
+      default = "logfmt";
+      description = lib.mdDoc ''
+        Output format of log messages. One of: [logfmt, json]
+      '';
+    };
+
+    webConfigFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Path to configuration file that can enable TLS or authentication.
+      '';
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      description = lib.mdDoc ''
+        Extra commandline options when launching Prometheus.
+      '';
+    };
+
+  };
+
+  serviceOpts = {
+    after = [ "pgbouncer.service" ];
+      serviceConfig = let
+      startScript = pkgs.writeShellScriptBin "pgbouncer-start" "${concatStringsSep " " ([
+            "${pkgs.prometheus-pgbouncer-exporter}/bin/pgbouncer_exporter"
+            "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
+            "--pgBouncer.connectionString ${if cfg.connectionStringFile != null then
+            "$(head -n1 ${cfg.connectionStringFile})" else "${escapeShellArg cfg.connectionString}"}"
+          ]
+            ++ optionals (cfg.telemetryPath != null) [
+            "--web.telemetry-path ${escapeShellArg cfg.telemetryPath}"
+          ]
+            ++ optionals (cfg.pidFile != null) [
+            "--pgBouncer.pid-file= ${escapeShellArg cfg.pidFile}"
+          ]
+            ++ optionals (cfg.logLevel != null) [
+            "--log.level ${escapeShellArg cfg.logLevel}"
+          ]
+            ++ optionals (cfg.logFormat != null) [
+            "--log.format ${escapeShellArg cfg.logFormat}"
+          ]
+            ++ optionals (cfg.webSystemdSocket != false) [
+            "--web.systemd-socket ${escapeShellArg cfg.webSystemdSocket}"
+          ]
+            ++ optionals (cfg.webConfigFile != null) [
+            "--web.config.file ${escapeShellArg cfg.webConfigFile}"
+          ]
+            ++ cfg.extraFlags)}";
+      in
+      {
+        ExecStart = "${startScript}/bin/pgbouncer-start";
+      };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/php-fpm.nix b/nixos/modules/services/monitoring/prometheus/exporters/php-fpm.nix
index 8f6942002f79f..8238f1ac1856e 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/php-fpm.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/php-fpm.nix
@@ -10,7 +10,7 @@ let
 in {
   port = 9253;
   extraOpts = {
-    package = lib.mkPackageOptionMD pkgs "prometheus-php-fpm-exporter" {};
+    package = lib.mkPackageOption pkgs "prometheus-php-fpm-exporter" {};
 
     telemetryPath = lib.mkOption {
       type = lib.types.str;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/ping.nix b/nixos/modules/services/monitoring/prometheus/exporters/ping.nix
new file mode 100644
index 0000000000000..af78b6bef6258
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/ping.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.ping;
+
+  settingsFormat = pkgs.formats.yaml {};
+  configFile = settingsFormat.generate "config.yml" cfg.settings;
+in
+{
+  port = 9427;
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+
+    settings = mkOption {
+      type = settingsFormat.type;
+      default = {};
+
+      description = lib.mdDoc ''
+        Configuration for ping_exporter, see
+        <https://github.com/czerwonk/ping_exporter>
+        for supported values.
+      '';
+    };
+  };
+
+  serviceOpts = {
+    serviceConfig = {
+      # ping-exporter needs `CAP_NET_RAW` to run as non root https://github.com/czerwonk/ping_exporter#running-as-non-root-user
+      CapabilityBoundingSet = [ "CAP_NET_RAW" ];
+      AmbientCapabilities = [ "CAP_NET_RAW" ];
+      ExecStart = ''
+        ${pkgs.prometheus-ping-exporter}/bin/ping_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          --config.path="${configFile}" \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
index f95412efd7dd3..20ee2e4b32380 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
@@ -15,15 +15,7 @@ in
 {
   port = 9221;
   extraOpts = {
-    package = mkOption {
-      type = types.package;
-      default = pkgs.prometheus-pve-exporter;
-      defaultText = literalExpression "pkgs.prometheus-pve-exporter";
-      example = literalExpression "pkgs.prometheus-pve-exporter";
-      description = lib.mdDoc ''
-        The package to use for prometheus-pve-exporter
-      '';
-    };
+    package = mkPackageOption pkgs "prometheus-pve-exporter" { };
 
     environmentFile = mkOption {
       type = with types; nullOr path;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix b/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix
new file mode 100644
index 0000000000000..b9ab305f7c082
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/sabnzbd.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, options }:
+
+let
+  inherit (lib) mkOption types;
+  cfg = config.services.prometheus.exporters.sabnzbd;
+in
+{
+  port = 9387;
+
+  extraOpts = {
+    servers = mkOption {
+      description = "List of sabnzbd servers to connect to.";
+      type = types.listOf (types.submodule {
+        options = {
+          baseUrl = mkOption {
+            type = types.str;
+            description = "Base URL of the sabnzbd server.";
+            example = "http://localhost:8080/sabnzbd";
+          };
+          apiKeyFile = mkOption {
+            type = types.str;
+            description = ''
+              The path to a file containing the API key.
+              The file is securely passed to the service by leveraging systemd credentials.
+              No special permissions need to be set on this file.
+            '';
+            example = "/run/secrets/sabnzbd_apikey";
+          };
+        };
+      });
+    };
+  };
+
+  serviceOpts =
+    let
+      servers = lib.zipAttrs cfg.servers;
+      credentials = lib.imap0 (i: v: { name = "apikey-${toString i}"; path = v; }) servers.apiKeyFile;
+    in
+    {
+      serviceConfig.LoadCredential = builtins.map ({ name, path }: "${name}:${path}") credentials;
+
+      environment = {
+        METRICS_PORT = toString cfg.port;
+        METRICS_ADDR = cfg.listenAddress;
+        SABNZBD_BASEURLS = lib.concatStringsSep "," servers.baseUrl;
+      };
+
+      script =
+        let
+          apiKeys = lib.concatStringsSep "," (builtins.map (cred: "$(< $CREDENTIALS_DIRECTORY/${cred.name})") credentials);
+        in
+        ''
+          export SABNZBD_APIKEYS="${apiKeys}"
+          exec ${lib.getExe pkgs.prometheus-sabnzbd-exporter}
+        '';
+    };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index edc6e4b5022a5..840ce493ee812 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -4,6 +4,25 @@ with lib;
 
 let
   cfg = config.services.prometheus.exporters.snmp;
+
+  # This ensures that we can deal with string paths, path types and
+  # store-path strings with context.
+  coerceConfigFile = file:
+    if (builtins.isPath file) || (lib.isStorePath file) then
+      file
+    else
+      (lib.warn ''
+        ${logPrefix}: configuration file "${file}" is being copied to the nix-store.
+        If you would like to avoid that, please set enableConfigCheck to false.
+        '' /. + file);
+
+  checkConfig = file:
+    pkgs.runCommandLocal "checked-snmp-exporter-config.yml" {
+      nativeBuildInputs = [ pkgs.buildPackages.prometheus-snmp-exporter ];
+    } ''
+      ln -s ${coerceConfigFile file} $out
+      snmp_exporter --dry-run --config.file $out
+    '';
 in
 {
   port = 9116;
@@ -24,15 +43,23 @@ in
         Snmp exporter configuration as nix attribute set. Mutually exclusive with 'configurationPath' option.
       '';
       example = {
-        "default" = {
-          "version" = 2;
-          "auth" = {
-            "community" = "public";
-          };
+        auths.public_v2 = {
+          community = "public";
+          version = 2;
         };
       };
     };
 
+    enableConfigCheck = mkOption {
+      type = types.bool;
+      default = true;
+      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.
+      '';
+    };
+
     logFormat = mkOption {
       type = types.enum ["logfmt" "json"];
       default = "logfmt";
@@ -50,9 +77,13 @@ in
     };
   };
   serviceOpts = let
-    configFile = if cfg.configurationPath != null
-                 then cfg.configurationPath
-                 else "${pkgs.writeText "snmp-exporter-conf.yml" (builtins.toJSON cfg.configuration)}";
+    uncheckedConfigFile = if cfg.configurationPath != null
+                          then cfg.configurationPath
+                          else "${pkgs.writeText "snmp-exporter-conf.yml" (builtins.toJSON cfg.configuration)}";
+    configFile = if cfg.enableConfigCheck then
+      checkConfig uncheckedConfigFile
+    else
+      uncheckedConfigFile;
     in {
     serviceConfig = {
       ExecStart = ''
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
index c98dcd9f64bfb..9b7590314936e 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -11,7 +11,7 @@ in {
     ({ options.warnings = options.warnings; options.assertions = options.assertions; })
   ];
   extraOpts = {
-    verbose = mkEnableOption (lib.mdDoc "Verbose logging mode for prometheus-wireguard-exporter");
+    verbose = mkEnableOption (lib.mdDoc "verbose logging mode for prometheus-wireguard-exporter");
 
     wireguardConfig = mkOption {
       type = with types; nullOr (either path str);
diff --git a/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
index f5c114c92752f..e93924e4fba87 100644
--- a/nixos/modules/services/monitoring/prometheus/pushgateway.nix
+++ b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -23,14 +23,7 @@ in {
     services.prometheus.pushgateway = {
       enable = mkEnableOption (lib.mdDoc "Prometheus Pushgateway");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.prometheus-pushgateway;
-        defaultText = literalExpression "pkgs.prometheus-pushgateway";
-        description = lib.mdDoc ''
-          Package that should be used for the prometheus pushgateway.
-        '';
-      };
+      package = mkPackageOption pkgs "prometheus-pushgateway" { };
 
       web.listen-address = mkOption {
         type = types.nullOr types.str;
diff --git a/nixos/modules/services/monitoring/scollector.nix b/nixos/modules/services/monitoring/scollector.nix
index 48be309c95996..0011d56a066a6 100644
--- a/nixos/modules/services/monitoring/scollector.nix
+++ b/nixos/modules/services/monitoring/scollector.nix
@@ -40,14 +40,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.scollector;
-        defaultText = literalExpression "pkgs.scollector";
-        description = lib.mdDoc ''
-          scollector binary to use.
-        '';
-      };
+      package = mkPackageOption pkgs "scollector" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/monitoring/smartd.nix b/nixos/modules/services/monitoring/smartd.nix
index 1e654cad5dd2a..8b79ac0e0c1ea 100644
--- a/nixos/modules/services/monitoring/smartd.nix
+++ b/nixos/modules/services/monitoring/smartd.nix
@@ -19,7 +19,7 @@ let
       {
       ${pkgs.coreutils}/bin/cat << EOF
       From: smartd on ${host} <${nm.sender}>
-      To: undisclosed-recipients:;
+      To: ${nm.recipient}
       Subject: $SMARTD_SUBJECT
 
       $SMARTD_FULLMESSAGE
diff --git a/nixos/modules/services/monitoring/snmpd.nix b/nixos/modules/services/monitoring/snmpd.nix
new file mode 100644
index 0000000000000..f2d3953e6a620
--- /dev/null
+++ b/nixos/modules/services/monitoring/snmpd.nix
@@ -0,0 +1,83 @@
+{ pkgs, config, lib, ... }:
+
+let
+  cfg = config.services.snmpd;
+  configFile = if cfg.configText != "" then
+    pkgs.writeText "snmpd.cfg" ''
+      ${cfg.configText}
+    '' else null;
+in {
+  options.services.snmpd = {
+    enable = lib.mkEnableOption "snmpd";
+
+    package = lib.mkPackageOption pkgs "net-snmp" {};
+
+    listenAddress = lib.mkOption {
+      type = lib.types.str;
+      default = "0.0.0.0";
+      description = lib.mdDoc ''
+        The address to listen on for SNMP and AgentX messages.
+      '';
+      example = "127.0.0.1";
+    };
+
+    port = lib.mkOption {
+      type = lib.types.port;
+      default = 161;
+      description = lib.mdDoc ''
+        The port to listen on for SNMP and AgentX messages.
+      '';
+    };
+
+    openFirewall = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Open port in firewall for snmpd.
+      '';
+    };
+
+    configText = lib.mkOption {
+      type = lib.types.lines;
+      default = "";
+      description = lib.mdDoc ''
+        The contents of the snmpd.conf. If the {option}`configFile` option
+        is set, this value will be ignored.
+
+        Note that the contents of this option will be added to the Nix
+        store as world-readable plain text, {option}`configFile` can be used in
+        addition to a secret management tool to protect sensitive data.
+      '';
+    };
+
+    configFile = lib.mkOption {
+      type = lib.types.path;
+      default = configFile;
+      defaultText = lib.literalMD "The value of {option}`configText`.";
+      description = lib.mdDoc ''
+        Path to the snmpd.conf file. By default, if {option}`configText` is set,
+        a config file will be automatically generated.
+      '';
+    };
+
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services."snmpd" = {
+      description = "Simple Network Management Protocol (SNMP) daemon.";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${lib.getExe' cfg.package "snmpd"} -f -Lo -c ${cfg.configFile} ${cfg.listenAddress}:${toString cfg.port}";
+      };
+    };
+
+    networking.firewall.allowedUDPPorts = lib.mkIf cfg.openFirewall [
+      cfg.port
+    ];
+  };
+
+  meta.maintainers = [ lib.maintainers.eliandoran ];
+
+}
diff --git a/nixos/modules/services/monitoring/teamviewer.nix b/nixos/modules/services/monitoring/teamviewer.nix
index 9b1278317943d..7c45247aa6d5a 100644
--- a/nixos/modules/services/monitoring/teamviewer.nix
+++ b/nixos/modules/services/monitoring/teamviewer.nix
@@ -30,6 +30,7 @@ in
       description = "TeamViewer remote control daemon";
 
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       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 913e599c189a3..3bab8aba7bd60 100644
--- a/nixos/modules/services/monitoring/telegraf.nix
+++ b/nixos/modules/services/monitoring/telegraf.nix
@@ -13,12 +13,7 @@ in {
     services.telegraf = {
       enable = mkEnableOption (lib.mdDoc "telegraf server");
 
-      package = mkOption {
-        default = pkgs.telegraf;
-        defaultText = literalExpression "pkgs.telegraf";
-        description = lib.mdDoc "Which telegraf derivation to use";
-        type = types.package;
-      };
+      package = mkPackageOption pkgs "telegraf" { };
 
       environmentFiles = mkOption {
         type = types.listOf types.path;
@@ -53,6 +48,10 @@ in {
 
   ###### implementation
   config = mkIf config.services.telegraf.enable {
+    services.telegraf.extraConfig = {
+      inputs = {};
+      outputs = {};
+    };
     systemd.services.telegraf = let
       finalConfigFile = if config.services.telegraf.environmentFiles == []
                         then configFile
@@ -60,7 +59,9 @@ in {
     in {
       description = "Telegraf Agent";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
+      path = lib.optional (config.services.telegraf.extraConfig.inputs ? procstat) pkgs.procps;
       serviceConfig = {
         EnvironmentFile = config.services.telegraf.environmentFiles;
         ExecStartPre = lib.optional (config.services.telegraf.environmentFiles != [])
diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix
index e6d8afc66624f..02502816ef5d7 100644
--- a/nixos/modules/services/monitoring/thanos.nix
+++ b/nixos/modules/services/monitoring/thanos.nix
@@ -1,14 +1,37 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    collect
+    concatLists
+    concatStringsSep
+    flip
+    getAttrFromPath
+    hasPrefix
+    isList
+    length
+    literalExpression
+    literalMD
+    mapAttrsRecursiveCond
+    mapAttrsToList
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkMerge
+    mkOption
+    mkPackageOption
+    optional
+    optionalAttrs
+    optionalString
+    types
+    ;
+
   cfg = config.services.thanos;
 
   nullOpt = type: description: mkOption {
     type = types.nullOr type;
     default = null;
-    description = lib.mdDoc description;
+    description = mdDoc description;
   };
 
   optionToArgs = opt: v  : optional (v != null)  ''--${opt}="${toString v}"'';
@@ -32,7 +55,7 @@ let
     option = mkOption {
       type = types.bool;
       default = false;
-      description = lib.mdDoc description;
+      description = mdDoc description;
     };
   };
 
@@ -41,7 +64,7 @@ let
     option = mkOption {
       type = types.listOf types.str;
       default = [];
-      description = lib.mdDoc description;
+      description = mdDoc description;
     };
   };
 
@@ -50,7 +73,7 @@ let
     option = mkOption {
       type = types.attrsOf types.str;
       default = {};
-      description = lib.mdDoc description;
+      description = mdDoc description;
     };
   };
 
@@ -59,7 +82,7 @@ let
     option = mkOption {
       type = types.str;
       inherit default;
-      description = lib.mdDoc description;
+      description = mdDoc description;
     };
   };
 
@@ -86,7 +109,7 @@ let
     defaultText = literalMD ''
       calculated from `config.services.thanos.${cmd}`
     '';
-    description = lib.mdDoc ''
+    description = mdDoc ''
       Arguments to the `thanos ${cmd}` command.
 
       Defaults to a list of arguments formed by converting the structured
@@ -127,10 +150,10 @@ let
             if config.services.thanos.<cmd>.tracing.config == null then null
             else toString (toYAML "tracing.yaml" config.services.thanos.<cmd>.tracing.config);
           '';
-          description = lib.mdDoc ''
+          description = mdDoc ''
             Path to YAML file that contains tracing configuration.
 
-            See format details: <https://thanos.io/tracing.md/#configuration>
+            See format details: <https://thanos.io/tip/thanos/tracing.md/#configuration>
           '';
         };
       };
@@ -147,7 +170,7 @@ let
 
             If {option}`tracing.config-file` is set this option has no effect.
 
-            See format details: <https://thanos.io/tracing.md/#configuration>
+            See format details: <https://thanos.io/tip/thanos/tracing.md/#configuration>
           '';
         };
     };
@@ -192,10 +215,10 @@ let
             if config.services.thanos.<cmd>.objstore.config == null then null
             else toString (toYAML "objstore.yaml" config.services.thanos.<cmd>.objstore.config);
           '';
-          description = lib.mdDoc ''
+          description = mdDoc ''
             Path to YAML file that contains object store configuration.
 
-            See format details: <https://thanos.io/storage.md/#configuration>
+            See format details: <https://thanos.io/tip/thanos/storage.md/#configuring-access-to-object-storage>
           '';
         };
       };
@@ -212,7 +235,7 @@ let
 
             If {option}`objstore.config-file` is set this option has no effect.
 
-            See format details: <https://thanos.io/storage.md/#configuration>
+            See format details: <https://thanos.io/tip/thanos/storage.md/#configuring-access-to-object-storage>
           '';
         };
     };
@@ -231,7 +254,7 @@ let
           type = types.str;
           default = "/var/lib/${config.services.prometheus.stateDir}/data";
           defaultText = literalExpression ''"/var/lib/''${config.services.prometheus.stateDir}/data"'';
-          description = lib.mdDoc ''
+          description = mdDoc ''
             Data directory of TSDB.
           '';
         };
@@ -266,14 +289,14 @@ let
         Maximum size of concurrently allocatable bytes for chunks.
       '';
 
-      store.grpc.series-sample-limit = mkParamDef types.int 0 ''
-        Maximum amount of samples returned via a single Series call.
+      store.limits.request-samples = mkParamDef types.int 0 ''
+        The maximum samples allowed for a single Series request.
+        The Series call fails if this limit is exceeded.
 
         `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
-        lower, even though the maximum could be hit.
+        NOTE: For efficiency the limit is internally implemented as 'chunks limit'
+        considering each chunk contains a maximum of 120 samples.
       '';
 
       store.grpc.series-max-concurrency = mkParamDef types.int 20 ''
@@ -371,24 +394,24 @@ let
         Maximum number of queries processed concurrently by query node.
       '';
 
-      query.replica-label = mkParam types.str ''
-        Label to treat as a replica indicator along which data is
+      query.replica-labels = mkListParam "query.replica-label" ''
+        Labels to treat as a replica indicator along which data is
         deduplicated.
 
         Still you will be able to query without deduplication using
-        `dedup=false` parameter.
+        'dedup=false' parameter. Data includes time series, recording
+        rules, and alerting rules.
       '';
 
       selector-labels = mkAttrsParam "selector-label" ''
         Query selector labels that will be exposed in info endpoint.
       '';
 
-      store.addresses = mkListParam "store" ''
-        Addresses of statically configured store API servers.
+      endpoints = mkListParam "endpoint" ''
+        Addresses of statically configured Thanos API servers (repeatable).
 
-        The scheme may be prefixed with `dns+` or
-        `dnssrv+` to detect store API servers through
-        respective DNS lookups.
+        The scheme may be prefixed with 'dns+' or 'dnssrv+' to detect
+        Thanos API servers through respective DNS lookups.
       '';
 
       store.sd-files = mkListParam "store.sd-files" ''
@@ -430,6 +453,12 @@ let
       '';
     };
 
+    query-frontend = params.common cfg.query-frontend // {
+      query-frontend.downstream-url = mkParamDef types.str "http://localhost:9090" ''
+        URL of downstream Prometheus Query compatible API.
+      '';
+    };
+
     rule = params.common cfg.rule // params.objstore cfg.rule // {
 
       labels = mkAttrsParam "label" ''
@@ -447,7 +476,7 @@ let
         Rule files that should be used by rule manager. Can be in glob format.
       '';
 
-      eval-interval = mkParamDef types.str "30s" ''
+      eval-interval = mkParamDef types.str "1m" ''
         The default evaluation interval to use.
       '';
 
@@ -597,10 +626,6 @@ let
         to render all samples for a human eye anyway
       '';
 
-      block-sync-concurrency = mkParamDef types.int 20 ''
-        Number of goroutines to use when syncing block metadata from object storage.
-      '';
-
       compact.concurrency = mkParamDef types.int 1 ''
         Number of goroutines to use when compacting groups.
       '';
@@ -625,7 +650,7 @@ let
         Data directory relative to `/var/lib` of TSDB.
       '';
 
-      labels = mkAttrsParam "labels" ''
+      labels = mkAttrsParam "label" ''
         External labels to announce.
 
         This flag will be removed in the future when handling multiple tsdb
@@ -656,57 +681,56 @@ in {
 
   options.services.thanos = {
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.thanos;
-      defaultText = literalExpression "pkgs.thanos";
-      description = lib.mdDoc ''
-        The thanos package that should be used.
-      '';
-    };
+    package = mkPackageOption pkgs "thanos" {};
 
     sidecar = paramsToOptions params.sidecar // {
       enable = mkEnableOption
-        (lib.mdDoc "the Thanos sidecar for Prometheus server");
+        (mdDoc "the Thanos sidecar for Prometheus server");
       arguments = mkArgumentsOption "sidecar";
     };
 
     store = paramsToOptions params.store // {
       enable = mkEnableOption
-        (lib.mdDoc "the Thanos store node giving access to blocks in a bucket provider.");
+        (mdDoc "the Thanos store node giving access to blocks in a bucket provider.");
       arguments = mkArgumentsOption "store";
     };
 
     query = paramsToOptions params.query // {
       enable = mkEnableOption
-        (lib.mdDoc ("the Thanos query node exposing PromQL enabled Query API " +
+        (mdDoc ("the Thanos query node exposing PromQL enabled Query API " +
          "with data retrieved from multiple store nodes"));
       arguments = mkArgumentsOption "query";
     };
 
+    query-frontend = paramsToOptions params.query-frontend // {
+      enable = mkEnableOption
+        (mdDoc ("the Thanos query frontend implements a service deployed in front of queriers to
+          improve query parallelization and caching."));
+      arguments = mkArgumentsOption "query-frontend";
+    };
+
     rule = paramsToOptions params.rule // {
       enable = mkEnableOption
-        (lib.mdDoc ("the Thanos ruler service which evaluates Prometheus rules against" +
+        (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
-        (lib.mdDoc "the Thanos compactor which continuously compacts blocks in an object store bucket");
+        (mdDoc "the Thanos compactor which continuously compacts blocks in an object store bucket");
       arguments = mkArgumentsOption "compact";
     };
 
     downsample = paramsToOptions params.downsample // {
       enable = mkEnableOption
-        (lib.mdDoc "the Thanos downsampler which continuously downsamples blocks in an object store bucket");
+        (mdDoc "the Thanos downsampler which continuously downsamples blocks in an object store bucket");
       arguments = mkArgumentsOption "downsample";
     };
 
     receive = paramsToOptions params.receive // {
       enable = mkEnableOption
-        (lib.mdDoc ("the Thanos receiver which accept Prometheus remote write API requests " +
-         "and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)"));
+        (mdDoc ("the Thanos receiver which accept Prometheus remote write API requests and write to local tsdb"));
       arguments = mkArgumentsOption "receive";
     };
   };
@@ -736,6 +760,7 @@ in {
           User = "prometheus";
           Restart = "always";
           ExecStart = thanos "sidecar";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         };
       };
     })
@@ -751,6 +776,7 @@ in {
             StateDirectory = cfg.store.stateDir;
             Restart = "always";
             ExecStart = thanos "store";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           };
         };
       }
@@ -764,6 +790,20 @@ in {
           DynamicUser = true;
           Restart = "always";
           ExecStart = thanos "query";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        };
+      };
+    })
+
+    (mkIf cfg.query-frontend.enable {
+      systemd.services.thanos-query-frontend = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          Restart = "always";
+          ExecStart = thanos "query-frontend";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         };
       };
     })
@@ -779,6 +819,7 @@ in {
             StateDirectory = cfg.rule.stateDir;
             Restart = "always";
             ExecStart = thanos "rule";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           };
         };
       }
@@ -797,6 +838,7 @@ in {
               DynamicUser = true;
               StateDirectory = cfg.compact.stateDir;
               ExecStart = thanos "compact";
+              ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
             };
           } // optionalAttrs (!wait) { inherit (cfg.compact) startAt; };
       }
@@ -813,6 +855,7 @@ in {
             StateDirectory = cfg.downsample.stateDir;
             Restart = "always";
             ExecStart = thanos "downsample";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           };
         };
       }
@@ -829,6 +872,7 @@ in {
             StateDirectory = cfg.receive.stateDir;
             Restart = "always";
             ExecStart = thanos "receive";
+            ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           };
         };
       }
diff --git a/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix
index bb11b6a1c1d01..63afb5deb5bd4 100644
--- a/nixos/modules/services/monitoring/ups.nix
+++ b/nixos/modules/services/monitoring/ups.nix
@@ -6,9 +6,83 @@ with lib;
 
 let
   cfg = config.power.ups;
-in
+  defaultPort = 3493;
+
+  nutFormat = {
+
+    type = with lib.types; let
+
+      singleAtom = nullOr (oneOf [
+        bool
+        int
+        float
+        str
+      ]) // {
+        description = "atom (null, bool, int, float or string)";
+      };
+
+      in attrsOf (oneOf [
+        singleAtom
+        (listOf (nonEmptyListOf singleAtom))
+      ]);
+
+    generate = name: value:
+      let
+        normalizedValue =
+          lib.mapAttrs (key: val:
+            if lib.isList val
+            then forEach val (elem: if lib.isList elem then elem else [elem])
+            else
+              if val == null
+              then []
+              else [[val]]
+          ) value;
+
+        mkValueString = concatMapStringsSep " " (v:
+          let str = generators.mkValueStringDefault {} v;
+          in
+            # Quote the value if it has spaces and isn't already quoted.
+            if (hasInfix " " str) && !(hasPrefix "\"" str && hasSuffix "\"" str)
+            then "\"${str}\""
+            else str
+        );
+
+      in pkgs.writeText name (lib.generators.toKeyValue {
+        mkKeyValue = generators.mkKeyValueDefault { inherit mkValueString; } " ";
+        listsAsDuplicateKeys = true;
+      } normalizedValue);
+
+  };
+
+  installSecrets = source: target: secrets:
+    pkgs.writeShellScript "installSecrets.sh" ''
+      install -m0600 -D ${source} "${target}"
+      ${concatLines (forEach secrets (name: ''
+        ${pkgs.replace-secret}/bin/replace-secret \
+          '@${name}@' \
+          "$CREDENTIALS_DIRECTORY/${name}" \
+          "${target}"
+      ''))}
+      chmod u-w "${target}"
+    '';
+
+  upsmonConf = nutFormat.generate "upsmon.conf" cfg.upsmon.settings;
+
+  upsdUsers = pkgs.writeText "upsd.users" (let
+    # This looks like INI, but it's not quite because the
+    # 'upsmon' option lacks a '='. See: man upsd.users
+    userConfig = name: user: concatStringsSep "\n      " (concatLists [
+      [
+        "[${name}]"
+        "password = \"@upsdusers_password_${name}@\""
+      ]
+      (optional (user.upsmon != null) "upsmon ${user.upsmon}")
+      (forEach user.actions (action: "actions = ${action}"))
+      (forEach user.instcmds (instcmd: "instcmds = ${instcmd}"))
+    ]);
+  in concatStringsSep "\n\n" (mapAttrsToList userConfig cfg.users));
+
 
-let
   upsOptions = {name, config, ...}:
   {
     options = {
@@ -95,6 +169,213 @@ let
     };
   };
 
+  listenOptions = {
+    options = {
+      address = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Address of the interface for `upsd` to listen on.
+          See `man upsd.conf` for details.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = defaultPort;
+        description = lib.mdDoc ''
+          TCP port for `upsd` to listen on.
+          See `man upsd.conf` for details.
+        '';
+      };
+    };
+  };
+
+  upsdOptions = {
+    options = {
+      enable = mkOption {
+        type = types.bool;
+        defaultText = literalMD "`true` if `mode` is one of `standalone`, `netserver`";
+        description = mdDoc "Whether to enable `upsd`.";
+      };
+
+      listen = mkOption {
+        type = with types; listOf (submodule listenOptions);
+        default = [];
+        example = [
+          {
+            address = "192.168.50.1";
+          }
+          {
+            address = "::1";
+            port = 5923;
+          }
+        ];
+        description = lib.mdDoc ''
+          Address of the interface for `upsd` to listen on.
+          See `man upsd` for details`.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = lib.mdDoc ''
+          Additional lines to add to `upsd.conf`.
+        '';
+      };
+    };
+
+    config = {
+      enable = mkDefault (elem cfg.mode [ "standalone" "netserver" ]);
+    };
+  };
+
+
+  monitorOptions = { name, config, ... }: {
+    options = {
+      system = mkOption {
+        type = types.str;
+        default = name;
+        description = lib.mdDoc ''
+          Identifier of the UPS to monitor, in this form: `<upsname>[@<hostname>[:<port>]]`
+          See `upsmon.conf` for details.
+        '';
+      };
+
+      powerValue = mkOption {
+        type = types.int;
+        default = 1;
+        description = lib.mdDoc ''
+          Number of power supplies that the UPS feeds on this system.
+          See `upsmon.conf` for details.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          Username from `upsd.users` for accessing this UPS.
+          See `upsmon.conf` for details.
+        '';
+      };
+
+      passwordFile = mkOption {
+        type = types.str;
+        defaultText = literalMD "power.ups.users.\${user}.passwordFile";
+        description = lib.mdDoc ''
+          The full path to a file containing the password from
+          `upsd.users` for accessing this UPS. The password file
+          is read on service start.
+          See `upsmon.conf` for details.
+        '';
+      };
+
+      type = mkOption {
+        type = types.str;
+        default = "master";
+        description = lib.mdDoc ''
+          The relationship with `upsd`.
+          See `upsmon.conf` for details.
+        '';
+      };
+    };
+
+    config = {
+      passwordFile = mkDefault cfg.users.${config.user}.passwordFile;
+    };
+  };
+
+  upsmonOptions = {
+    options = {
+      enable = mkOption {
+        type = types.bool;
+        defaultText = literalMD "`true` if `mode` is one of `standalone`, `netserver`, `netclient`";
+        description = mdDoc "Whether to enable `upsmon`.";
+      };
+
+      monitor = mkOption {
+        type = with types; attrsOf (submodule monitorOptions);
+        default = {};
+        description = lib.mdDoc ''
+          Set of UPS to monitor. See `man upsmon.conf` for details.
+        '';
+      };
+
+      settings = mkOption {
+        type = nutFormat.type;
+        default = {};
+        defaultText = literalMD ''
+          {
+            MINSUPPLIES = 1;
+            RUN_AS_USER = "root";
+            NOTIFYCMD = "''${pkgs.nut}/bin/upssched";
+            SHUTDOWNCMD = "''${pkgs.systemd}/bin/shutdown now";
+          }
+        '';
+        description = mdDoc "Additional settings to add to `upsmon.conf`.";
+        example = literalMD ''
+          {
+            MINSUPPLIES = 2;
+            NOTIFYFLAG = [
+              [ "ONLINE" "SYSLOG+EXEC" ]
+              [ "ONBATT" "SYSLOG+EXEC" ]
+            ];
+          }
+        '';
+      };
+    };
+
+    config = {
+      enable = mkDefault (elem cfg.mode [ "standalone" "netserver" "netclient" ]);
+      settings = {
+        RUN_AS_USER = "root"; # TODO: replace 'root' by another username.
+        MINSUPPLIES = mkDefault 1;
+        NOTIFYCMD = mkDefault "${pkgs.nut}/bin/upssched";
+        SHUTDOWNCMD = mkDefault "${pkgs.systemd}/bin/shutdown now";
+        MONITOR = flip mapAttrsToList cfg.upsmon.monitor (name: monitor: with monitor; [ system powerValue user "\"@upsmon_password_${name}@\"" type ]);
+      };
+    };
+  };
+
+  userOptions = {
+    options = {
+      passwordFile = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          The full path to a file that contains the user's (clear text)
+          password. The password file is read on service start.
+        '';
+      };
+
+      actions = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = lib.mdDoc ''
+          Allow the user to do certain things with upsd.
+          See `man upsd.users` for details.
+        '';
+      };
+
+      instcmds = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = lib.mdDoc ''
+          Let the user initiate specific instant commands. Use "ALL" to grant all commands automatically. For the full list of what your UPS supports, use "upscmd -l".
+          See `man upsd.users` for details.
+        '';
+      };
+
+      upsmon = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = lib.mdDoc ''
+          Add the necessary actions for a upsmon process to work.
+          See `man upsd.users` for details.
+        '';
+      };
+    };
+  };
+
 in
 
 
@@ -103,19 +384,14 @@ in
     # powerManagement.powerDownCommands
 
     power.ups = {
-      enable = mkOption {
-        default = false;
-        type = with types; bool;
-        description = lib.mdDoc ''
-          Enables support for Power Devices, such as Uninterruptible Power
-          Supplies, Power Distribution Units and Solar Controllers.
-        '';
-      };
+      enable = mkEnableOption (lib.mdDoc ''
+        Enables support for Power Devices, such as Uninterruptible Power
+        Supplies, Power Distribution Units and Solar Controllers.
+      '');
 
-      # This option is not used yet.
       mode = mkOption {
         default = "standalone";
-        type = types.str;
+        type = types.enum [ "none" "standalone" "netserver" "netclient" ];
         description = lib.mdDoc ''
           The MODE determines which part of the NUT is to be started, and
           which configuration files must be modified.
@@ -148,6 +424,13 @@ in
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Open ports in the firewall for `upsd`.
+        '';
+      };
 
       maxStartDelay = mkOption {
         default = 45;
@@ -161,6 +444,22 @@ in
         '';
       };
 
+      upsmon = mkOption {
+        default = {};
+        description = lib.mdDoc ''
+          Options for the `upsmon.conf` configuration file.
+        '';
+        type = types.submodule upsmonOptions;
+      };
+
+      upsd = mkOption {
+        default = {};
+        description = lib.mdDoc ''
+          Options for the `upsd.conf` configuration file.
+        '';
+        type = types.submodule upsdOptions;
+      };
+
       ups = mkOption {
         default = {};
         # see nut/etc/ups.conf.sample
@@ -172,46 +471,95 @@ in
         type = with types; attrsOf (submodule upsOptions);
       };
 
+      users = mkOption {
+        default = {};
+        description = lib.mdDoc ''
+          Users that can access upsd. See `man upsd.users`.
+        '';
+        type = with types; attrsOf (submodule userOptions);
+      };
+
     };
   };
 
   config = mkIf cfg.enable {
 
+    assertions = [
+      (let
+        totalPowerValue = foldl' add 0 (map (monitor: monitor.powerValue) (attrValues cfg.upsmon.monitor));
+        minSupplies = cfg.upsmon.settings.MINSUPPLIES;
+      in mkIf cfg.upsmon.enable {
+        assertion = totalPowerValue >= minSupplies;
+        message = ''
+          `power.ups.upsmon`: Total configured power value (${toString totalPowerValue}) must be at least MINSUPPLIES (${toString minSupplies}).
+        '';
+      })
+    ];
+
     environment.systemPackages = [ pkgs.nut ];
 
-    systemd.services.upsmon = {
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts =
+        if cfg.upsd.listen == []
+        then [ defaultPort ]
+        else unique (forEach cfg.upsd.listen (listen: listen.port));
+    };
+
+    systemd.services.upsmon = let
+      secrets = mapAttrsToList (name: monitor: "upsmon_password_${name}") cfg.upsmon.monitor;
+      createUpsmonConf = installSecrets upsmonConf "/run/nut/upsmon.conf" secrets;
+    in {
+      enable = cfg.upsmon.enable;
       description = "Uninterruptible Power Supplies (Monitor)";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      serviceConfig.Type = "forking";
-      script = "${pkgs.nut}/sbin/upsmon";
-      environment.NUT_CONFPATH = "/etc/nut/";
-      environment.NUT_STATEPATH = "/var/lib/nut/";
+      serviceConfig = {
+        Type = "forking";
+        ExecStartPre = "${createUpsmonConf}";
+        ExecStart = "${pkgs.nut}/sbin/upsmon";
+        ExecReload = "${pkgs.nut}/sbin/upsmon -c reload";
+        LoadCredential = mapAttrsToList (name: monitor: "upsmon_password_${name}:${monitor.passwordFile}") cfg.upsmon.monitor;
+      };
+      environment.NUT_CONFPATH = "/etc/nut";
+      environment.NUT_STATEPATH = "/var/lib/nut";
     };
 
-    systemd.services.upsd = {
+    systemd.services.upsd = let
+      secrets = mapAttrsToList (name: user: "upsdusers_password_${name}") cfg.users;
+      createUpsdUsers = installSecrets upsdUsers "/run/nut/upsd.users" secrets;
+    in {
+      enable = cfg.upsd.enable;
       description = "Uninterruptible Power Supplies (Daemon)";
       after = [ "network.target" "upsmon.service" ];
       wantedBy = [ "multi-user.target" ];
-      serviceConfig.Type = "forking";
-      # TODO: replace 'root' by another username.
-      script = "${pkgs.nut}/sbin/upsd -u root";
-      environment.NUT_CONFPATH = "/etc/nut/";
-      environment.NUT_STATEPATH = "/var/lib/nut/";
+      serviceConfig = {
+        Type = "forking";
+        ExecStartPre = "${createUpsdUsers}";
+        # TODO: replace 'root' by another username.
+        ExecStart = "${pkgs.nut}/sbin/upsd -u root";
+        ExecReload = "${pkgs.nut}/sbin/upsd -c reload";
+        LoadCredential = mapAttrsToList (name: user: "upsdusers_password_${name}:${user.passwordFile}") cfg.users;
+      };
+      environment.NUT_CONFPATH = "/etc/nut";
+      environment.NUT_STATEPATH = "/var/lib/nut";
+      restartTriggers = [
+        config.environment.etc."nut/upsd.conf".source
+      ];
     };
 
     systemd.services.upsdrv = {
+      enable = cfg.upsd.enable;
       description = "Uninterruptible Power Supplies (Register all UPS)";
       after = [ "upsd.service" ];
       wantedBy = [ "multi-user.target" ];
-      # TODO: replace 'root' by another username.
-      script = "${pkgs.nut}/bin/upsdrvctl -u root start";
       serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = true;
+        # TODO: replace 'root' by another username.
+        ExecStart = "${pkgs.nut}/bin/upsdrvctl -u root start";
       };
-      environment.NUT_CONFPATH = "/etc/nut/";
-      environment.NUT_STATEPATH = "/var/lib/nut/";
+      environment.NUT_CONFPATH = "/etc/nut";
+      environment.NUT_STATEPATH = "/var/lib/nut";
     };
 
     environment.etc = {
@@ -223,28 +571,26 @@ in
         ''
           maxstartdelay = ${toString cfg.maxStartDelay}
 
-          ${flip concatStringsSep (forEach (attrValues cfg.ups) (ups: ups.summary)) "
-
-          "}
+          ${concatStringsSep "\n\n" (forEach (attrValues cfg.ups) (ups: ups.summary))}
+        '';
+      "nut/upsd.conf".source = pkgs.writeText "upsd.conf"
+        ''
+          ${concatStringsSep "\n" (forEach cfg.upsd.listen (listen: "LISTEN ${listen.address} ${toString listen.port}"))}
+          ${cfg.upsd.extraConfig}
         '';
       "nut/upssched.conf".source = cfg.schedulerRules;
-      # These file are containing private information and thus should not
-      # be stored inside the Nix store.
-      /*
-      "nut/upsd.conf".source = "";
-      "nut/upsd.users".source = "";
-      "nut/upsmon.conf".source = "";
-      */
+      "nut/upsd.users".source = "/run/nut/upsd.users";
+      "nut/upsmon.conf".source = "/run/nut/upsmon.conf";
     };
 
     power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample";
 
-    system.activationScripts.upsSetup = stringAfter [ "users" "groups" ]
-      ''
-        # Used to store pid files of drivers.
-        mkdir -p /var/state/ups
-      '';
+    systemd.tmpfiles.rules = [
+      "d /var/state/ups -"
+      "d /var/lib/nut 700"
+    ];
 
+    services.udev.packages = [ pkgs.nut ];
 
 /*
     users.users.nut =
diff --git a/nixos/modules/services/monitoring/uptime-kuma.nix b/nixos/modules/services/monitoring/uptime-kuma.nix
index 7027046b24253..f3a41de7536a8 100644
--- a/nixos/modules/services/monitoring/uptime-kuma.nix
+++ b/nixos/modules/services/monitoring/uptime-kuma.nix
@@ -13,12 +13,7 @@ in
     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.";
-      };
+      package = mkPackageOption pkgs "uptime-kuma" { };
 
       appriseSupport = mkEnableOption (mdDoc "apprise support for notifications");
 
diff --git a/nixos/modules/services/monitoring/vmagent.nix b/nixos/modules/services/monitoring/vmagent.nix
index 0e2ffb31c57cf..bd3ef756959d3 100644
--- a/nixos/modules/services/monitoring/vmagent.nix
+++ b/nixos/modules/services/monitoring/vmagent.nix
@@ -23,14 +23,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      default = pkgs.vmagent;
-      defaultText = lib.literalMD "pkgs.vmagent";
-      type = types.package;
-      description = lib.mdDoc ''
-        vmagent package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "vmagent" { };
 
     dataDir = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/monitoring/vmalert.nix b/nixos/modules/services/monitoring/vmalert.nix
index 27fb34e199b54..1c64f7e100fad 100644
--- a/nixos/modules/services/monitoring/vmalert.nix
+++ b/nixos/modules/services/monitoring/vmalert.nix
@@ -22,14 +22,7 @@ in
   options.services.vmalert = {
     enable = mkEnableOption (mdDoc "vmalert");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.victoriametrics;
-      defaultText = "pkgs.victoriametrics";
-      description = mdDoc ''
-        The VictoriaMetrics derivation to use.
-      '';
-    };
+    package = mkPackageOption pkgs "victoriametrics" { };
 
     settings = mkOption {
       type = types.submodule {
diff --git a/nixos/modules/services/monitoring/watchdogd.nix b/nixos/modules/services/monitoring/watchdogd.nix
new file mode 100644
index 0000000000000..e8d104651c6ad
--- /dev/null
+++ b/nixos/modules/services/monitoring/watchdogd.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.watchdogd;
+
+  mkPluginOpts = plugin: defWarn: defCrit: {
+    enabled = mkEnableOption "watchdogd plugin ${plugin}";
+    interval = mkOption {
+      type = types.ints.unsigned;
+      default = 300;
+      description = ''
+        Amount of seconds between every poll.
+      '';
+    };
+    logmark = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to log current stats every poll interval.
+      '';
+    };
+    warning = mkOption {
+      type = types.numbers.nonnegative;
+      default = defWarn;
+      description = ''
+        The high watermark level. Alert sent to log.
+      '';
+    };
+    critical = mkOption {
+      type = types.numbers.nonnegative;
+      default = defCrit;
+      description = ''
+        The critical watermark level. Alert sent to log, followed by reboot or script action.
+      '';
+    };
+  };
+in {
+  options.services.watchdogd = {
+    enable = mkEnableOption "watchdogd, an advanced system & process supervisor";
+    package = mkPackageOption pkgs "watchdogd" { };
+
+    settings = mkOption {
+      type = with types; submodule {
+        freeformType = let
+          valueType = oneOf [
+            bool
+            int
+            float
+            str
+          ];
+        in attrsOf (either valueType (attrsOf valueType));
+
+        options = {
+          timeout = mkOption {
+            type = types.ints.unsigned;
+            default = 15;
+            description = ''
+              The WDT timeout before reset.
+            '';
+          };
+          interval = mkOption {
+            type = types.ints.unsigned;
+            default = 5;
+            description = ''
+              The kick interval, i.e. how often {manpage}`watchdogd(8)` should reset the WDT timer.
+            '';
+          };
+
+          safe-exit = mkOption {
+            type = types.bool;
+            default = true;
+            description = ''
+              With {var}`safeExit` enabled, the daemon will ask the driver to disable the WDT before exiting.
+              However, some WDT drivers (or hardware) may not support this.
+            '';
+          };
+
+          filenr = mkPluginOpts "filenr" 0.9 1.0;
+
+          loadavg = mkPluginOpts "loadavg" 1.0 2.0;
+
+          meminfo = mkPluginOpts "meminfo" 0.9 0.95;
+        };
+      };
+      default = { };
+      description = ''
+        Configuration to put in {file}`watchdogd.conf`.
+        See {manpage}`watchdogd.conf(5)` for more details.
+      '';
+    };
+  };
+
+  config = let
+    toConfig = attrs: concatStringsSep "\n" (mapAttrsToList toValue attrs);
+
+    toValue = name: value:
+      if isAttrs value
+        then pipe value [
+          (mapAttrsToList toValue)
+          (map (s: "  ${s}"))
+          (concatStringsSep "\n")
+          (s: "${name} {\n${s}\n}")
+        ]
+      else if isBool value
+        then "${name} = ${boolToString value}"
+      else if any (f: f value) [isString isInt isFloat]
+        then "${name} = ${toString value}"
+      else throw ''
+        Found invalid type in `services.watchdogd.settings`: '${typeOf value}'
+      '';
+
+    watchdogdConf = pkgs.writeText "watchdogd.conf" (toConfig cfg.settings);
+  in mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.watchdogd = {
+      documentation = [
+        "man:watchdogd(8)"
+        "man:watchdogd.conf(5)"
+      ];
+      wantedBy = [ "multi-user.target" ];
+      description = "Advanced system & process supervisor";
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${cfg.package}/bin/watchdogd -n -f ${watchdogdConf}";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ vifino ];
+}
diff --git a/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix
index b497ecbcdb6ca..b195366123abe 100644
--- a/nixos/modules/services/monitoring/zabbix-agent.nix
+++ b/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -3,7 +3,7 @@
 let
   cfg = config.services.zabbixAgent;
 
-  inherit (lib) mkDefault mkEnableOption mkIf mkMerge mkOption;
+  inherit (lib) mkDefault mkEnableOption mkPackageOption mkIf mkMerge mkOption;
   inherit (lib) attrValues concatMapStringsSep literalExpression optionalString types;
   inherit (lib.generators) toKeyValue;
 
@@ -31,12 +31,7 @@ in
     services.zabbixAgent = {
       enable = mkEnableOption (lib.mdDoc "the Zabbix Agent");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.zabbix.agent;
-        defaultText = literalExpression "pkgs.zabbix.agent";
-        description = lib.mdDoc "The Zabbix package to use.";
-      };
+      package = mkPackageOption pkgs [ "zabbix" "agent" ] { };
 
       extraPackages = mkOption {
         type = types.listOf types.package;
diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix
index 85da416ba6c33..503e81b48a587 100644
--- a/nixos/modules/services/monitoring/zabbix-proxy.nix
+++ b/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -203,7 +203,7 @@ in
       { assertion = !config.services.zabbixServer.enable;
         message = "Please choose one of services.zabbixServer or services.zabbixProxy.";
       }
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.zabbixProxy.database.user must be set to ${user} if services.zabbixProxy.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -252,7 +252,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix
index 2b50280e3969a..0607188d21319 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -191,7 +191,7 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
         message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -240,7 +240,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/network-filesystems/ceph.nix b/nixos/modules/services/network-filesystems/ceph.nix
index aad03728b203d..222905223b590 100644
--- a/nixos/modules/services/network-filesystems/ceph.nix
+++ b/nixos/modules/services/network-filesystems/ceph.nix
@@ -210,7 +210,7 @@ in
           to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
         '';
       };
-      package = mkPackageOptionMD pkgs "ceph" { };
+      package = mkPackageOption pkgs "ceph" { };
       extraConfig = mkOption {
         type = with types; attrsOf str;
         default = {};
@@ -231,7 +231,7 @@ in
           to the id part in ceph i.e. [ "name1" ] would result in mon.name1
         '';
       };
-      package = mkPackageOptionMD pkgs "ceph" { };
+      package = mkPackageOption pkgs "ceph" { };
       extraConfig = mkOption {
         type = with types; attrsOf str;
         default = {};
@@ -252,7 +252,7 @@ in
           to the id part in ceph i.e. [ "name1" ] would result in osd.name1
         '';
       };
-      package = mkPackageOptionMD pkgs "ceph" { };
+      package = mkPackageOption pkgs "ceph" { };
       extraConfig = mkOption {
         type = with types; attrsOf str;
         default = {
@@ -280,7 +280,7 @@ in
           to the id part in ceph i.e. [ "name1" ] would result in mds.name1
         '';
       };
-      package = mkPackageOptionMD pkgs "ceph" { };
+      package = mkPackageOption pkgs "ceph" { };
       extraConfig = mkOption {
         type = with types; attrsOf str;
         default = {};
@@ -292,7 +292,7 @@ in
 
     rgw = {
       enable = mkEnableOption (lib.mdDoc "Ceph RadosGW daemon");
-      package = mkPackageOptionMD pkgs "ceph" { };
+      package = mkPackageOption pkgs "ceph" { };
       daemons = mkOption {
         type = with types; listOf str;
         default = [];
diff --git a/nixos/modules/services/network-filesystems/drbd.nix b/nixos/modules/services/network-filesystems/drbd.nix
index e74ed391d48e3..79a1b768b4615 100644
--- a/nixos/modules/services/network-filesystems/drbd.nix
+++ b/nixos/modules/services/network-filesystems/drbd.nix
@@ -55,8 +55,8 @@ let cfg = config.services.drbd; in
       wants = [ "systemd-udev.settle.service" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.drbd}/sbin/drbdadm up all";
-        ExecStop = "${pkgs.drbd}/sbin/drbdadm down all";
+        ExecStart = "${pkgs.drbd}/bin/drbdadm up all";
+        ExecStop = "${pkgs.drbd}/bin/drbdadm down all";
       };
     };
   };
diff --git a/nixos/modules/services/network-filesystems/eris-server.nix b/nixos/modules/services/network-filesystems/eris-server.nix
index 66eccfac408c4..104676a52c61f 100644
--- a/nixos/modules/services/network-filesystems/eris-server.nix
+++ b/nixos/modules/services/network-filesystems/eris-server.nix
@@ -3,6 +3,7 @@
 let
   cfg = config.services.eris-server;
   stateDirectoryPath = "\${STATE_DIRECTORY}";
+  nullOrStr = with lib.types; nullOr str;
 in {
 
   options.services.eris-server = {
@@ -26,7 +27,7 @@ in {
     };
 
     listenCoap = lib.mkOption {
-      type = lib.types.str;
+      type = nullOrStr;
       default = ":5683";
       example = "[::1]:5683";
       description = ''
@@ -39,8 +40,8 @@ in {
     };
 
     listenHttp = lib.mkOption {
-      type = lib.types.str;
-      default = "";
+      type = nullOrStr;
+      default = null;
       example = "[::1]:8080";
       description = "Server HTTP listen address. Do not listen by default.";
     };
@@ -58,8 +59,8 @@ in {
     };
 
     mountpoint = lib.mkOption {
-      type = lib.types.str;
-      default = "";
+      type = nullOrStr;
+      default = null;
       example = "/eris";
       description = ''
         Mountpoint for FUSE namespace that exposes "urn:eris:…" files.
@@ -69,33 +70,44 @@ in {
   };
 
   config = lib.mkIf cfg.enable {
+    assertions = [{
+      assertion = lib.strings.versionAtLeast cfg.package.version "20231219";
+      message =
+        "Version of `config.services.eris-server.package` is incompatible with this module";
+    }];
+
     systemd.services.eris-server = let
-      cmd =
-        "${cfg.package}/bin/eris-go server --coap '${cfg.listenCoap}' --http '${cfg.listenHttp}' ${
-          lib.optionalString cfg.decode "--decode "
-        }${
-          lib.optionalString (cfg.mountpoint != "")
-          ''--mountpoint "${cfg.mountpoint}" ''
-        }${lib.strings.escapeShellArgs cfg.backends}";
+      cmd = "${cfg.package}/bin/eris-go server"
+        + (lib.optionalString (cfg.listenCoap != null)
+          " --coap '${cfg.listenCoap}'")
+        + (lib.optionalString (cfg.listenHttp != null)
+          " --http '${cfg.listenHttp}'")
+        + (lib.optionalString cfg.decode " --decode")
+        + (lib.optionalString (cfg.mountpoint != null)
+          " --mountpoint '${cfg.mountpoint}'");
     in {
       description = "ERIS block server";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      script = lib.mkIf (cfg.mountpoint != "") ''
+      environment.ERIS_STORE_URL = toString cfg.backends;
+      script = lib.mkIf (cfg.mountpoint != null) ''
         export PATH=${config.security.wrapperDir}:$PATH
         ${cmd}
       '';
       serviceConfig = let
-        umounter = lib.mkIf (cfg.mountpoint != "")
+        umounter = lib.mkIf (cfg.mountpoint != null)
           "-${config.security.wrapperDir}/fusermount -uz ${cfg.mountpoint}";
-      in {
-        ExecStartPre = umounter;
-        ExecStart = lib.mkIf (cfg.mountpoint == "") cmd;
-        ExecStopPost = umounter;
-        Restart = "always";
-        RestartSec = 20;
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
-      };
+      in if (cfg.mountpoint == null) then {
+        ExecStart = cmd;
+      } else
+        {
+          ExecStartPre = umounter;
+          ExecStopPost = umounter;
+        } // {
+          Restart = "always";
+          RestartSec = 20;
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        };
     };
   };
 
diff --git a/nixos/modules/services/network-filesystems/kubo.nix b/nixos/modules/services/network-filesystems/kubo.nix
index 5a355f3441d8a..10162c1633e7b 100644
--- a/nixos/modules/services/network-filesystems/kubo.nix
+++ b/nixos/modules/services/network-filesystems/kubo.nix
@@ -52,7 +52,7 @@ let
 
   multiaddrsToListenStreams = addrIn:
     let
-      addrs = if builtins.typeOf addrIn == "list"
+      addrs = if builtins.isList addrIn
       then addrIn else [ addrIn ];
       unfilteredResult = map multiaddrToListenStream addrs;
     in
@@ -60,7 +60,7 @@ let
 
   multiaddrsToListenDatagrams = addrIn:
     let
-      addrs = if builtins.typeOf addrIn == "list"
+      addrs = if builtins.isList addrIn
       then addrIn else [ addrIn ];
       unfilteredResult = map multiaddrToListenDatagram addrs;
     in
@@ -99,14 +99,14 @@ in
 
     services.kubo = {
 
-      enable = mkEnableOption (lib.mdDoc "Interplanetary File System (WARNING: may cause severe network degradation)");
+      enable = mkEnableOption (lib.mdDoc ''
+        the Interplanetary File System (WARNING: may cause severe network degradation).
+        NOTE: after enabling this option and rebuilding your system, you need to log out
+        and back in for the `IPFS_PATH` environment variable to be present in your shell.
+        Until you do that, the CLI tools won't be able to talk to the daemon by default
+      '');
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.kubo;
-        defaultText = literalExpression "pkgs.kubo";
-        description = lib.mdDoc "Which Kubo package to use.";
-      };
+      package = mkPackageOption pkgs "kubo" { };
 
       user = mkOption {
         type = types.str;
@@ -152,18 +152,6 @@ in
         description = lib.mdDoc "Whether Kubo should try to run the fs-repo-migration at startup.";
       };
 
-      ipfsMountDir = mkOption {
-        type = types.str;
-        default = "/ipfs";
-        description = lib.mdDoc "Where to mount the IPFS namespace to";
-      };
-
-      ipnsMountDir = mkOption {
-        type = types.str;
-        default = "/ipns";
-        description = lib.mdDoc "Where to mount the IPNS namespace to";
-      };
-
       enableGC = mkOption {
         type = types.bool;
         default = false;
@@ -203,15 +191,25 @@ in
               default = [
                 "/ip4/0.0.0.0/tcp/4001"
                 "/ip6/::/tcp/4001"
-                "/ip4/0.0.0.0/udp/4001/quic"
                 "/ip4/0.0.0.0/udp/4001/quic-v1"
                 "/ip4/0.0.0.0/udp/4001/quic-v1/webtransport"
-                "/ip6/::/udp/4001/quic"
                 "/ip6/::/udp/4001/quic-v1"
                 "/ip6/::/udp/4001/quic-v1/webtransport"
               ];
               description = lib.mdDoc "Where Kubo listens for incoming p2p connections";
             };
+
+            Mounts.IPFS = mkOption {
+              type = types.str;
+              default = "/ipfs";
+              description = lib.mdDoc "Where to mount the IPFS namespace to";
+            };
+
+            Mounts.IPNS = mkOption {
+              type = types.str;
+              default = "/ipns";
+              description = lib.mdDoc "Where to mount the IPNS namespace to";
+            };
           };
         };
         description = lib.mdDoc ''
@@ -281,16 +279,17 @@ in
       {
         assertion = !((lib.versionAtLeast cfg.package.version "0.21") && (builtins.hasAttr "Experimental" cfg.settings) && (builtins.hasAttr "AcceleratedDHTClient" cfg.settings.Experimental));
         message = ''
-    The `services.kubo.settings.Experimental.AcceleratedDHTClient` option was renamed to `services.kubo.settings.Routing.AcceleratedDHTClient` in Kubo 0.21.
-  '';
+          The `services.kubo.settings.Experimental.AcceleratedDHTClient` option was renamed to `services.kubo.settings.Routing.AcceleratedDHTClient` in Kubo 0.21.
+        '';
       }
     ];
 
     environment.systemPackages = [ cfg.package ];
     environment.variables.IPFS_PATH = fakeKuboRepo;
 
-    # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
+    # https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes
     boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000;
+    boot.kernel.sysctl."net.core.wmem_max" = mkDefault 2500000;
 
     programs.fuse = mkIf cfg.autoMount {
       userAllowOther = true;
@@ -316,8 +315,8 @@ in
     systemd.tmpfiles.rules = [
       "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
     ] ++ optionals cfg.autoMount [
-      "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -"
-      "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.settings.Mounts.IPFS}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.settings.Mounts.IPNS}' - ${cfg.user} ${cfg.group} - -"
     ];
 
     # The hardened systemd unit breaks the fuse-mount function according to documentation in the unit file itself
@@ -327,8 +326,6 @@ in
 
     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 = {
@@ -359,8 +356,8 @@ in
           ipfs --offline config replace -
       '';
       postStop = mkIf cfg.autoMount ''
-        # After an unclean shutdown the fuse mounts at cfg.ipnsMountDir and cfg.ipfsMountDir are locked
-        umount --quiet '${cfg.ipnsMountDir}' '${cfg.ipfsMountDir}' || true
+        # After an unclean shutdown the fuse mounts at cfg.settings.Mounts.IPFS and cfg.settings.Mounts.IPNS are locked
+        umount --quiet '${cfg.settings.Mounts.IPFS}' '${cfg.settings.Mounts.IPNS}' || true
       '';
       serviceConfig = {
         ExecStart = [ "" "${cfg.package}/bin/ipfs daemon ${kuboFlags}" ];
@@ -368,6 +365,8 @@ in
         Group = cfg.group;
         StateDirectory = "";
         ReadWritePaths = optionals (!cfg.autoMount) [ "" cfg.dataDir ];
+        # Make sure the socket units are started before ipfs.service
+        Sockets = [ "ipfs-gateway.socket" "ipfs-api.socket" ];
       } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
     } // optionalAttrs (!cfg.startWhenNeeded) {
       wantedBy = [ "default.target" ];
@@ -410,8 +409,8 @@ in
     (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" "ipfsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPFS" ])
+    (mkRenamedOptionModule [ "services" "ipfs" "ipnsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPNS" ])
     (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" ])
@@ -426,5 +425,7 @@ in
     (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" ])
+    (mkRenamedOptionModule [ "services" "kubo" "ipfsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPFS" ])
+    (mkRenamedOptionModule [ "services" "kubo" "ipnsMountDir" ] [ "services" "kubo" "settings" "Mounts" "IPNS" ])
   ];
 }
diff --git a/nixos/modules/services/network-filesystems/litestream/default.nix b/nixos/modules/services/network-filesystems/litestream/default.nix
index 6e2ec1ccaa3c7..afc38fcebcff9 100644
--- a/nixos/modules/services/network-filesystems/litestream/default.nix
+++ b/nixos/modules/services/network-filesystems/litestream/default.nix
@@ -10,12 +10,7 @@ in
   options.services.litestream = {
     enable = mkEnableOption (lib.mdDoc "litestream");
 
-    package = mkOption {
-      description = lib.mdDoc "Package to use.";
-      default = pkgs.litestream;
-      defaultText = literalExpression "pkgs.litestream";
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "litestream" { };
 
     settings = mkOption {
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/network-filesystems/openafs/client.nix b/nixos/modules/services/network-filesystems/openafs/client.nix
index bb0fee087e62e..02c3482ec657b 100644
--- a/nixos/modules/services/network-filesystems/openafs/client.nix
+++ b/nixos/modules/services/network-filesystems/openafs/client.nix
@@ -215,6 +215,7 @@ in
     systemd.services.afsd = {
       description = "AFS client";
       wantedBy = [ "multi-user.target" ];
+      wants = lib.optional (!cfg.startDisconnected) "network-online.target";
       after = singleton (if cfg.startDisconnected then  "network.target" else "network-online.target");
       serviceConfig = { RemainAfterExit = true; };
       restartIfChanged = false;
diff --git a/nixos/modules/services/network-filesystems/openafs/server.nix b/nixos/modules/services/network-filesystems/openafs/server.nix
index ad0fd7835670d..14bdf2f338651 100644
--- a/nixos/modules/services/network-filesystems/openafs/server.nix
+++ b/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -5,7 +5,7 @@ with import ./lib.nix { inherit config lib pkgs; };
 
 let
   inherit (lib) concatStringsSep literalExpression mkIf mkOption mkEnableOption
-  optionalString types;
+  mkPackageOption optionalString types;
 
   bosConfig = pkgs.writeText "BosConfig" (''
     restrictmode 1
@@ -101,12 +101,7 @@ in {
         description = lib.mdDoc "Definition of all cell-local database server machines.";
       };
 
-      package = mkOption {
-        default = pkgs.openafs;
-        defaultText = literalExpression "pkgs.openafs";
-        type = types.package;
-        description = lib.mdDoc "OpenAFS package for the server binaries";
-      };
+      package = mkPackageOption pkgs "openafs" { };
 
       roles = {
         fileserver = {
@@ -177,13 +172,13 @@ in {
 
         backup = {
           enable = mkEnableOption (lib.mdDoc ''
-            Backup server role. When using OpenAFS built-in buserver, use in conjunction with the
+            the backup server role. When using OpenAFS built-in buserver, use in conjunction with the
             `database` role to maintain the Backup
             Database. Normally only used in conjunction with tape storage
             or IBM's Tivoli Storage Manager.
 
             For a modern backup server, enable this role and see
-            {option}`enableFabs`.
+            {option}`enableFabs`
           '');
 
           enableFabs = mkEnableOption (lib.mdDoc ''
diff --git a/nixos/modules/services/network-filesystems/orangefs/server.nix b/nixos/modules/services/network-filesystems/orangefs/server.nix
index e20e7975ebaa0..085b64e4c040c 100644
--- a/nixos/modules/services/network-filesystems/orangefs/server.nix
+++ b/nixos/modules/services/network-filesystems/orangefs/server.nix
@@ -192,7 +192,7 @@ in {
     # orangefs daemon will run as user
     users.users.orangefs = {
       isSystemUser = true;
-      group = "orangfs";
+      group = "orangefs";
     };
     users.groups.orangefs = {};
 
diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix
index 1310a374abd0b..ef368ddbeefd5 100644
--- a/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixos/modules/services/network-filesystems/samba.nix
@@ -39,7 +39,7 @@ let
   daemonService = appName: args:
     { description = "Samba Service Daemon ${appName}";
 
-      after = [ (mkIf (cfg.enableNmbd && "${appName}" == "smbd") "samba-nmbd.service") ];
+      after = [ (mkIf (cfg.enableNmbd && "${appName}" == "smbd") "samba-nmbd.service") "network.target" ];
       requiredBy = [ "samba.target" ];
       partOf = [ "samba.target" ];
 
@@ -120,14 +120,8 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.samba;
-        defaultText = literalExpression "pkgs.samba";
-        example = literalExpression "pkgs.samba4Full";
-        description = lib.mdDoc ''
-          Defines which package should be used for the samba server.
-        '';
+      package = mkPackageOption pkgs "samba" {
+        example = "samba4Full";
       };
 
       invalidUsers = mkOption {
@@ -160,7 +154,7 @@ in
       };
 
       securityType = mkOption {
-        type = types.str;
+        type = types.enum [ "auto" "user" "domain" "ads" ];
         default = "user";
         description = lib.mdDoc "Samba security type";
       };
diff --git a/nixos/modules/services/network-filesystems/tahoe.nix b/nixos/modules/services/network-filesystems/tahoe.nix
index 14c0a3d4725f1..d016d4a38fb98 100644
--- a/nixos/modules/services/network-filesystems/tahoe.nix
+++ b/nixos/modules/services/network-filesystems/tahoe.nix
@@ -32,14 +32,7 @@ in
                 If specified, the port should be included.
               '';
             };
-            package = mkOption {
-              default = pkgs.tahoelafs;
-              defaultText = literalExpression "pkgs.tahoelafs";
-              type = types.package;
-              description = lib.mdDoc ''
-                The package to use for the Tahoe LAFS daemon.
-              '';
-            };
+            package = mkPackageOption pkgs "tahoelafs" { };
           };
         });
         description = lib.mdDoc ''
@@ -176,14 +169,7 @@ in
                 URL of the accounts server.
               '';
             };
-            package = mkOption {
-              default = pkgs.tahoelafs;
-              defaultText = literalExpression "pkgs.tahoelafs";
-              type = types.package;
-              description = lib.mdDoc ''
-                The package to use for the Tahoe LAFS daemon.
-              '';
-            };
+            package = mkPackageOption pkgs "tahoelafs" { };
           };
         });
         description = lib.mdDoc ''
diff --git a/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixos/modules/services/network-filesystems/xtreemfs.nix
index 926c3c3bd5233..866661cf4e6f8 100644
--- a/nixos/modules/services/network-filesystems/xtreemfs.nix
+++ b/nixos/modules/services/network-filesystems/xtreemfs.nix
@@ -176,7 +176,7 @@ in
           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
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
         replication = {
@@ -218,7 +218,7 @@ in
             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
+              For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
             '';
           };
         };
@@ -319,7 +319,7 @@ in
           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
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
         replication = {
@@ -361,7 +361,7 @@ in
             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
+              For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
             '';
           };
         };
@@ -438,7 +438,7 @@ in
           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
+            For more options: https://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
           '';
         };
       };
diff --git a/nixos/modules/services/networking/acme-dns.nix b/nixos/modules/services/networking/acme-dns.nix
index 5c53fa2cc4f15..08fde65e4ca4e 100644
--- a/nixos/modules/services/networking/acme-dns.nix
+++ b/nixos/modules/services/networking/acme-dns.nix
@@ -12,7 +12,7 @@ let
     mdDoc
     mkEnableOption
     mkOption
-    mkPackageOptionMD
+    mkPackageOption
     types
     ;
   domain = "acme-dns.example.com";
@@ -21,7 +21,7 @@ in
   options.services.acme-dns = {
     enable = mkEnableOption (mdDoc "acme-dns");
 
-    package = mkPackageOptionMD pkgs "acme-dns" { };
+    package = mkPackageOption pkgs "acme-dns" { };
 
     settings = mkOption {
       description = mdDoc ''
diff --git a/nixos/modules/services/networking/adguardhome.nix b/nixos/modules/services/networking/adguardhome.nix
index 1701e5b439c18..399d838ccc699 100644
--- a/nixos/modules/services/networking/adguardhome.nix
+++ b/nixos/modules/services/networking/adguardhome.nix
@@ -17,6 +17,7 @@ let
     text = builtins.toJSON cfg.settings;
     checkPhase = "${pkgs.adguardhome}/bin/adguardhome -c $out --check-config";
   };
+  defaultBindPort = 3000;
 
 in
 {
@@ -86,7 +87,7 @@ in
             '';
           };
           bind_port = mkOption {
-            default = 3000;
+            default = defaultBindPort;
             type = port;
             description = lib.mdDoc ''
               Port to serve HTTP pages on.
@@ -169,6 +170,6 @@ in
       };
     };
 
-    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.bind_port ];
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.bind_port or defaultBindPort ];
   };
 }
diff --git a/nixos/modules/services/networking/alice-lg.nix b/nixos/modules/services/networking/alice-lg.nix
index 06b9ac89f12fc..fbf127d9410f6 100644
--- a/nixos/modules/services/networking/alice-lg.nix
+++ b/nixos/modules/services/networking/alice-lg.nix
@@ -11,7 +11,7 @@ in
     services.alice-lg = {
       enable = mkEnableOption (lib.mdDoc "Alice Looking Glass");
 
-      package = mkPackageOptionMD pkgs "alice-lg" { };
+      package = mkPackageOption pkgs "alice-lg" { };
 
       settings = mkOption {
         type = settingsFormat.type;
diff --git a/nixos/modules/services/networking/asterisk.nix b/nixos/modules/services/networking/asterisk.nix
index 5a1d03f072117..78a69efc86af8 100644
--- a/nixos/modules/services/networking/asterisk.nix
+++ b/nixos/modules/services/networking/asterisk.nix
@@ -139,7 +139,7 @@ in
           path.
 
           See
-          <http://www.asterisk.org/community/documentation>
+          <https://www.asterisk.org/community/documentation/>
           for more examples of what is possible here.
         '';
       };
@@ -163,12 +163,7 @@ in
           Additional command line arguments to pass to Asterisk.
         '';
       };
-      package = mkOption {
-        type = types.package;
-        default = pkgs.asterisk;
-        defaultText = literalExpression "pkgs.asterisk";
-        description = lib.mdDoc "The Asterisk package to use.";
-      };
+      package = mkPackageOption pkgs "asterisk" { };
     };
   };
 
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index bdbf9aad9accf..782681018116c 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -42,6 +42,7 @@ in
 {
   imports = [
     (lib.mkRenamedOptionModule [ "services" "avahi" "interfaces" ] [ "services" "avahi" "allowInterfaces" ])
+    (lib.mkRenamedOptionModule [ "services" "avahi" "nssmdns" ] [ "services" "avahi" "nssmdns4" ])
   ];
 
   options.services.avahi = {
@@ -56,14 +57,7 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.avahi;
-      defaultText = literalExpression "pkgs.avahi";
-      description = lib.mdDoc ''
-        The avahi package to use for running the daemon.
-      '';
-    };
+    package = mkPackageOption pkgs "avahi" { };
 
     hostName = mkOption {
       type = types.str;
@@ -100,8 +94,7 @@ in
 
     ipv6 = mkOption {
       type = types.bool;
-      default = config.networking.enableIPv6;
-      defaultText = literalExpression "config.networking.enableIPv6";
+      default = false;
       description = lib.mdDoc "Whether to use IPv6.";
     };
 
@@ -225,16 +218,31 @@ in
       };
     };
 
-    nssmdns = mkOption {
+    nssmdns4 = mkOption {
       type = types.bool;
       default = false;
       description = lib.mdDoc ''
-        Whether to enable the mDNS NSS (Name Service Switch) plug-in.
+        Whether to enable the mDNS NSS (Name Service Switch) plug-in for IPv4.
         Enabling it allows applications to resolve names in the `.local`
         domain by transparently querying the Avahi daemon.
       '';
     };
 
+    nssmdns6 = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to enable the mDNS NSS (Name Service Switch) plug-in for IPv6.
+        Enabling it allows applications to resolve names in the `.local`
+        domain by transparently querying the Avahi daemon.
+
+        ::: {.note}
+        Due to the fact that most mDNS responders only register local IPv4 addresses,
+        most user want to leave this option disabled to avoid long timeouts when applications first resolve the none existing IPv6 address.
+        :::
+      '';
+    };
+
     cacheEntriesMax = mkOption {
       type = types.nullOr types.int;
       default = null;
@@ -263,10 +271,19 @@ in
 
     users.groups.avahi = { };
 
-    system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
-    system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [
-      (mkBefore [ "mdns_minimal [NOTFOUND=return]" ]) # before resolve
-      (mkAfter [ "mdns" ]) # after dns
+    system.nssModules = optional (cfg.nssmdns4 || cfg.nssmdns6) pkgs.nssmdns;
+    system.nssDatabases.hosts = let
+      mdns = if (cfg.nssmdns4 && cfg.nssmdns6) then
+        "mdns"
+      else if (!cfg.nssmdns4 && cfg.nssmdns6) then
+        "mdns6"
+      else if (cfg.nssmdns4 && !cfg.nssmdns6) then
+        "mdns4"
+      else
+        "";
+    in optionals (cfg.nssmdns4 || cfg.nssmdns6) (mkMerge [
+      (mkBefore [ "${mdns}_minimal [NOTFOUND=return]" ]) # before resolve
+      (mkAfter [ "${mdns}" ]) # after dns
     ]);
 
     environment.systemPackages = [ cfg.package ];
diff --git a/nixos/modules/services/networking/bee.nix b/nixos/modules/services/networking/bee.nix
index add9861ebfcdf..962cfd30c3fe9 100644
--- a/nixos/modules/services/networking/bee.nix
+++ b/nixos/modules/services/networking/bee.nix
@@ -17,12 +17,8 @@ in {
     services.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 = lib.mdDoc "The package providing the bee binary for the service.";
+      package = mkPackageOption pkgs "bee" {
+        example = "bee-unstable";
       };
 
       settings = mkOption {
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index f1829747bb1e0..da8633d5066f7 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -118,12 +118,7 @@ in
       enable = mkEnableOption (lib.mdDoc "BIND domain name server");
 
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.bind;
-        defaultText = literalExpression "pkgs.bind";
-        description = lib.mdDoc "The BIND package to use.";
-      };
+      package = mkPackageOption pkgs "bind" { };
 
       cacheNetworks = mkOption {
         default = [ "127.0.0.0/24" ];
diff --git a/nixos/modules/services/networking/bird-lg.nix b/nixos/modules/services/networking/bird-lg.nix
index dc861dbfd11bd..be9f4101e6abe 100644
--- a/nixos/modules/services/networking/bird-lg.nix
+++ b/nixos/modules/services/networking/bird-lg.nix
@@ -51,12 +51,7 @@ 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.";
-      };
+      package = mkPackageOption pkgs "bird-lg" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix
index 9deeb7694d2ac..e25f5c7b03794 100644
--- a/nixos/modules/services/networking/bird.nix
+++ b/nixos/modules/services/networking/bird.nix
@@ -18,6 +18,13 @@ in
           <http://bird.network.cz/>
         '';
       };
+      autoReload = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Whether bird2 should be automatically reloaded when the configuration changes.
+        '';
+      };
       checkConfig = mkOption {
         type = types.bool;
         default = true;
@@ -68,7 +75,7 @@ in
     systemd.services.bird2 = {
       description = "BIRD Internet Routing Daemon";
       wantedBy = [ "multi-user.target" ];
-      reloadTriggers = [ config.environment.etc."bird/bird2.conf".source ];
+      reloadTriggers = lib.optional cfg.autoReload config.environment.etc."bird/bird2.conf".source;
       serviceConfig = {
         Type = "forking";
         Restart = "on-failure";
diff --git a/nixos/modules/services/networking/birdwatcher.nix b/nixos/modules/services/networking/birdwatcher.nix
index a129b7a2b4cf5..c8ebb22697643 100644
--- a/nixos/modules/services/networking/birdwatcher.nix
+++ b/nixos/modules/services/networking/birdwatcher.nix
@@ -8,12 +8,7 @@ in
 {
   options = {
     services.birdwatcher = {
-      package = mkOption {
-        type = types.package;
-        default = pkgs.birdwatcher;
-        defaultText = literalExpression "pkgs.birdwatcher";
-        description = lib.mdDoc "The Birdwatcher package to use.";
-      };
+      package = mkPackageOption pkgs "birdwatcher" { };
       enable = mkEnableOption (lib.mdDoc "Birdwatcher");
       flags = mkOption {
         default = [ ];
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
index a86d52b7202d8..59722e31c62ab 100644
--- a/nixos/modules/services/networking/bitcoind.nix
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -3,8 +3,7 @@
 with lib;
 
 let
-
-  eachBitcoind = config.services.bitcoind;
+  eachBitcoind = filterAttrs (bitcoindName: cfg: cfg.enable) config.services.bitcoind;
 
   rpcUserOpts = { name, ... }: {
     options = {
@@ -37,12 +36,7 @@ let
 
       enable = mkEnableOption (lib.mdDoc "Bitcoin daemon");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.bitcoind;
-        defaultText = literalExpression "pkgs.bitcoind";
-        description = lib.mdDoc "The package providing bitcoin binaries.";
-      };
+      package = mkPackageOption pkgs "bitcoind" { };
 
       configFile = mkOption {
         type = types.nullOr types.path;
@@ -204,6 +198,7 @@ in
         '';
       in {
         description = "Bitcoin daemon";
+        wants = [ "network-online.target" ];
         after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
diff --git a/nixos/modules/services/networking/blockbook-frontend.nix b/nixos/modules/services/networking/blockbook-frontend.nix
index 46b26195d2113..bf476d814140a 100644
--- a/nixos/modules/services/networking/blockbook-frontend.nix
+++ b/nixos/modules/services/networking/blockbook-frontend.nix
@@ -12,12 +12,7 @@ let
 
       enable = mkEnableOption (lib.mdDoc "blockbook-frontend application");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.blockbook;
-        defaultText = literalExpression "pkgs.blockbook";
-        description = lib.mdDoc "Which blockbook package to use.";
-      };
+      package = mkPackageOption pkgs "blockbook" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/networking/centrifugo.nix b/nixos/modules/services/networking/centrifugo.nix
new file mode 100644
index 0000000000000..7c6c9a362fd20
--- /dev/null
+++ b/nixos/modules/services/networking/centrifugo.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.centrifugo;
+
+  settingsFormat = pkgs.formats.json { };
+
+  configFile = settingsFormat.generate "centrifugo.json" cfg.settings;
+in
+{
+  options.services.centrifugo = {
+    enable = lib.mkEnableOption (lib.mdDoc "Centrifugo messaging server");
+
+    package = lib.mkPackageOption pkgs "centrifugo" { };
+
+    settings = lib.mkOption {
+      type = settingsFormat.type;
+      default = { };
+      description = lib.mdDoc ''
+        Declarative Centrifugo configuration. See the [Centrifugo
+        documentation] for a list of options.
+
+        [Centrifugo documentation]: https://centrifugal.dev/docs/server/configuration
+      '';
+    };
+
+    credentials = lib.mkOption {
+      type = lib.types.attrsOf lib.types.path;
+      default = { };
+      example = {
+        CENTRIFUGO_UNI_GRPC_TLS_KEY = "/run/keys/centrifugo-uni-grpc-tls.key";
+      };
+      description = lib.mdDoc ''
+        Environment variables with absolute paths to credentials files to load
+        on service startup.
+      '';
+    };
+
+    environmentFiles = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      description = lib.mdDoc ''
+        Files to load environment variables from. Options set via environment
+        variables take precedence over {option}`settings`.
+
+        See the [Centrifugo documentation] for the environment variable name
+        format.
+
+        [Centrifugo documentation]: https://centrifugal.dev/docs/server/configuration#os-environment-variables
+      '';
+    };
+
+    extraGroups = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ ];
+      example = [ "redis-centrifugo" ];
+      description = lib.mdDoc ''
+        Additional groups for the systemd service.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.centrifugo = {
+      description = "Centrifugo messaging server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        Type = "exec";
+
+        ExecStartPre = "${lib.getExe cfg.package} checkconfig --config ${configFile}";
+        ExecStart = "${lib.getExe cfg.package} --config ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
+        Restart = "always";
+        RestartSec = "1s";
+
+        # Copy files to the credentials directory with file name being the
+        # environment variable name. Note that "%d" specifier expands to the
+        # path of the credentials directory.
+        LoadCredential = lib.mapAttrsToList (name: value: "${name}:${value}") cfg.credentials;
+        Environment = lib.mapAttrsToList (name: _: "${name}=%d/${name}") cfg.credentials;
+
+        EnvironmentFile = cfg.environmentFiles;
+
+        SupplementaryGroups = cfg.extraGroups;
+
+        DynamicUser = true;
+        UMask = "0077";
+
+        ProtectHome = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        PrivateUsers = true;
+        PrivateDevices = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        DeviceAllow = [ "" ];
+        DevicePolicy = "closed";
+        CapabilityBoundingSet = [ "" ];
+        MemoryDenyWriteExecute = true;
+        LockPersonality = true;
+        SystemCallArchitectures = "native";
+        SystemCallErrorNumber = "EPERM";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/cgit.nix b/nixos/modules/services/networking/cgit.nix
index 7d1f12fa91469..3de2eb192ed14 100644
--- a/nixos/modules/services/networking/cgit.nix
+++ b/nixos/modules/services/networking/cgit.nix
@@ -102,7 +102,7 @@ in
         options = {
           enable = mkEnableOption (mdDoc "cgit");
 
-          package = mkPackageOptionMD pkgs "cgit" {};
+          package = mkPackageOption pkgs "cgit" {};
 
           nginx.virtualHost = mkOption {
             description = mdDoc "VirtualHost to serve cgit on, defaults to the attribute name.";
diff --git a/nixos/modules/services/networking/cloudflared.nix b/nixos/modules/services/networking/cloudflared.nix
index b3f0e37d8e9e3..80c60fdb80137 100644
--- a/nixos/modules/services/networking/cloudflared.nix
+++ b/nixos/modules/services/networking/cloudflared.nix
@@ -152,12 +152,7 @@ in
       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.";
-    };
+    package = mkPackageOption pkgs "cloudflared" { };
 
     tunnels = mkOption {
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/networking/connman.nix b/nixos/modules/services/networking/connman.nix
index 498991419579e..c626945ccd0c0 100644
--- a/nixos/modules/services/networking/connman.nix
+++ b/nixos/modules/services/networking/connman.nix
@@ -1,55 +1,59 @@
 { config, lib, pkgs, ... }:
 
-with pkgs;
-with lib;
-
 let
   cfg = config.services.connman;
   configFile = pkgs.writeText "connman.conf" ''
     [General]
-    NetworkInterfaceBlacklist=${concatStringsSep "," cfg.networkInterfaceBlacklist}
+    NetworkInterfaceBlacklist=${lib.concatStringsSep "," cfg.networkInterfaceBlacklist}
 
     ${cfg.extraConfig}
   '';
   enableIwd = cfg.wifi.backend == "iwd";
 in {
+  meta.maintainers = with lib.maintainers; [ AndersonTorres ];
 
   imports = [
-    (mkRenamedOptionModule [ "networking" "connman" ] [ "services" "connman" ])
+    (lib.mkRenamedOptionModule [ "networking" "connman" ] [ "services" "connman" ])
   ];
 
   ###### interface
 
   options = {
-
     services.connman = {
-
-      enable = mkOption {
-        type = types.bool;
+      enable = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc ''
           Whether to use ConnMan for managing your network connections.
         '';
       };
 
-      enableVPN = mkOption {
-        type = types.bool;
+      package = lib.mkOption {
+        type = lib.types.package;
+        description = lib.mdDoc "The connman package / build flavor";
+        default = pkgs.connman;
+        defaultText = lib.literalExpression "pkgs.connman";
+        example = lib.literalExpression "pkgs.connmanFull";
+      };
+
+      enableVPN = lib.mkOption {
+        type = lib.types.bool;
         default = true;
         description = lib.mdDoc ''
           Whether to enable ConnMan VPN service.
         '';
       };
 
-      extraConfig = mkOption {
-        type = types.lines;
+      extraConfig = lib.mkOption {
+        type = lib.types.lines;
         default = "";
         description = lib.mdDoc ''
           Configuration lines appended to the generated connman configuration file.
         '';
       };
 
-      networkInterfaceBlacklist = mkOption {
-        type = with types; listOf str;
+      networkInterfaceBlacklist = lib.mkOption {
+        type = with lib.types; listOf str;
         default = [ "vmnet" "vboxnet" "virbr" "ifb" "ve" ];
         description = lib.mdDoc ''
           Default blacklisted interfaces, this includes NixOS containers interfaces (ve).
@@ -57,8 +61,8 @@ in {
       };
 
       wifi = {
-        backend = mkOption {
-          type = types.enum [ "wpa_supplicant" "iwd" ];
+        backend = lib.mkOption {
+          type = lib.types.enum [ "wpa_supplicant" "iwd" ];
           default = "wpa_supplicant";
           description = lib.mdDoc ''
             Specify the Wi-Fi backend used.
@@ -67,31 +71,20 @@ in {
         };
       };
 
-      extraFlags = mkOption {
-        type = with types; listOf str;
+      extraFlags = lib.mkOption {
+        type = with lib.types; listOf str;
         default = [ ];
         example = [ "--nodnsproxy" ];
         description = lib.mdDoc ''
           Extra flags to pass to connmand
         '';
       };
-
-      package = mkOption {
-        type = types.package;
-        description = lib.mdDoc "The connman package / build flavor";
-        default = connman;
-        defaultText = literalExpression "pkgs.connman";
-        example = literalExpression "pkgs.connmanFull";
-      };
-
     };
-
   };
 
   ###### implementation
 
-  config = mkIf cfg.enable {
-
+  config = lib.mkIf cfg.enable {
     assertions = [{
       assertion = !config.networking.useDHCP;
       message = "You can not use services.connman with networking.useDHCP";
@@ -107,8 +100,8 @@ in {
     systemd.services.connman = {
       description = "Connection service";
       wantedBy = [ "multi-user.target" ];
-      after = [ "syslog.target" ] ++ optional enableIwd "iwd.service";
-      requires = optional enableIwd "iwd.service";
+      after = [ "syslog.target" ] ++ lib.optional enableIwd "iwd.service";
+      requires = lib.optional enableIwd "iwd.service";
       serviceConfig = {
         Type = "dbus";
         BusName = "net.connman";
@@ -117,13 +110,13 @@ in {
           "${cfg.package}/sbin/connmand"
           "--config=${configFile}"
           "--nodaemon"
-        ] ++ optional enableIwd "--wifi=iwd_agent"
+        ] ++ lib.optional enableIwd "--wifi=iwd_agent"
           ++ cfg.extraFlags);
         StandardOutput = "null";
       };
     };
 
-    systemd.services.connman-vpn = mkIf cfg.enableVPN {
+    systemd.services.connman-vpn = lib.mkIf cfg.enableVPN {
       description = "ConnMan VPN service";
       wantedBy = [ "multi-user.target" ];
       after = [ "syslog.target" ];
@@ -136,7 +129,7 @@ in {
       };
     };
 
-    systemd.services.net-connman-vpn = mkIf cfg.enableVPN {
+    systemd.services.net-connman-vpn = lib.mkIf cfg.enableVPN {
       description = "D-BUS Service";
       serviceConfig = {
         Name = "net.connman.vpn";
@@ -150,9 +143,9 @@ in {
     networking = {
       useDHCP = false;
       wireless = {
-        enable = mkIf (!enableIwd) true;
+        enable = lib.mkIf (!enableIwd) true;
         dbusControlled = true;
-        iwd = mkIf enableIwd {
+        iwd = lib.mkIf enableIwd {
           enable = true;
         };
       };
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index 955463b9031eb..1a0910fc93448 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -33,15 +33,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.consul;
-        defaultText = literalExpression "pkgs.consul";
-        description = lib.mdDoc ''
-          The package used for the Consul agent and CLI.
-        '';
-      };
-
+      package = mkPackageOption pkgs "consul" { };
 
       webUi = mkOption {
         type = types.bool;
@@ -128,12 +120,7 @@ in
       alerts = {
         enable = mkEnableOption (lib.mdDoc "consul-alerts");
 
-        package = mkOption {
-          description = lib.mdDoc "Package to use for consul-alerts.";
-          default = pkgs.consul-alerts;
-          defaultText = literalExpression "pkgs.consul-alerts";
-          type = types.package;
-        };
+        package = mkPackageOption pkgs "consul-alerts" { };
 
         listenAddr = mkOption {
           description = lib.mdDoc "Api listening address.";
diff --git a/nixos/modules/services/networking/coredns.nix b/nixos/modules/services/networking/coredns.nix
index f928cdf96143e..f6eec2f962dd1 100644
--- a/nixos/modules/services/networking/coredns.nix
+++ b/nixos/modules/services/networking/coredns.nix
@@ -23,11 +23,13 @@ in {
       '';
     };
 
-    package = mkOption {
-      default = pkgs.coredns;
-      defaultText = literalExpression "pkgs.coredns";
-      type = types.package;
-      description = lib.mdDoc "Coredns package to use.";
+    package = mkPackageOption pkgs "coredns" { };
+
+    extraArgs = mkOption {
+      default = [];
+      example = [ "-dns.port=53" ];
+      type = types.listOf types.str;
+      description = lib.mdDoc "Extra arguments to pass to coredns.";
     };
   };
 
@@ -44,7 +46,7 @@ in {
         AmbientCapabilities = "cap_net_bind_service";
         NoNewPrivileges = true;
         DynamicUser = true;
-        ExecStart = "${getBin cfg.package}/bin/coredns -conf=${configFile}";
+        ExecStart = "${getBin cfg.package}/bin/coredns -conf=${configFile} ${lib.escapeShellArgs cfg.extraArgs}";
         ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR1 $MAINPID";
         Restart = "on-failure";
       };
diff --git a/nixos/modules/services/networking/corerad.nix b/nixos/modules/services/networking/corerad.nix
index 0c6fb7a17cab7..33ea2862174e9 100644
--- a/nixos/modules/services/networking/corerad.nix
+++ b/nixos/modules/services/networking/corerad.nix
@@ -48,12 +48,7 @@ in {
       description = lib.mdDoc "Path to CoreRAD TOML configuration file.";
     };
 
-    package = mkOption {
-      default = pkgs.corerad;
-      defaultText = literalExpression "pkgs.corerad";
-      type = types.package;
-      description = lib.mdDoc "CoreRAD package to use.";
-    };
+    package = mkPackageOption pkgs "corerad" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/networking/create_ap.nix b/nixos/modules/services/networking/create_ap.nix
index e772cf21ec573..994aa6d36d2ae 100644
--- a/nixos/modules/services/networking/create_ap.nix
+++ b/nixos/modules/services/networking/create_ap.nix
@@ -8,7 +8,7 @@ let
 in {
   options = {
     services.create_ap = {
-      enable = mkEnableOption (lib.mdDoc "setup wifi hotspots using create_ap");
+      enable = mkEnableOption (lib.mdDoc "setting up wifi hotspots using create_ap");
       settings = mkOption {
         type = with types; attrsOf (oneOf [ int bool str ]);
         default = {};
diff --git a/nixos/modules/services/networking/dae.nix b/nixos/modules/services/networking/dae.nix
index 231c555b33030..404ce59741f8f 100644
--- a/nixos/modules/services/networking/dae.nix
+++ b/nixos/modules/services/networking/dae.nix
@@ -1,41 +1,170 @@
-{ config, pkgs, lib, ... }:
+{ config, lib, pkgs, ... }:
+
 let
   cfg = config.services.dae;
+  assets = cfg.assets;
+  genAssetsDrv = paths: pkgs.symlinkJoin {
+    name = "dae-assets";
+    inherit paths;
+  };
 in
 {
-  meta.maintainers = with lib.maintainers; [ pokon548 ];
+  meta.maintainers = with lib.maintainers; [ pokon548 oluceps ];
 
   options = {
-    services.dae = {
-      enable = lib.options.mkEnableOption (lib.mdDoc "the dae service");
-      package = lib.mkPackageOptionMD pkgs "dae" { };
-    };
-  };
+    services.dae = with lib;{
+      enable = mkEnableOption
+        (mdDoc "dae, a Linux high-performance transparent proxy solution based on eBPF");
+
+      package = mkPackageOption pkgs "dae" { };
+
 
-  config = lib.mkIf config.services.dae.enable {
-    networking.firewall.allowedTCPPorts = [ 12345 ];
-    networking.firewall.allowedUDPPorts = [ 12345 ];
+      assets = mkOption {
+        type = with types;(listOf path);
+        default = with pkgs; [ v2ray-geoip v2ray-domain-list-community ];
+        defaultText = literalExpression "with pkgs; [ v2ray-geoip v2ray-domain-list-community ]";
+        description = mdDoc ''
+          Assets required to run dae.
+        '';
+      };
+
+      assetsPath = mkOption {
+        type = types.str;
+        default = "${genAssetsDrv assets}/share/v2ray";
+        defaultText = literalExpression ''
+          (symlinkJoin {
+              name = "dae-assets";
+              paths = assets;
+          })/share/v2ray
+        '';
+        description = mdDoc ''
+          The path which contains geolocation database.
+          This option will override `assets`.
+        '';
+      };
 
-    systemd.services.dae = {
-      unitConfig = {
-        Description = "dae Service";
-        Documentation = "https://github.com/daeuniverse/dae";
-        After = [ "network-online.target" "systemd-sysctl.service" ];
-        Wants = [ "network-online.target" ];
+      openFirewall = mkOption {
+        type = with types; submodule {
+          options = {
+            enable = mkEnableOption (mdDoc "opening {option}`port` in the firewall");
+            port = mkOption {
+              type = types.port;
+              description = ''
+                Port to be opened. Consist with field `tproxy_port` in config file.
+              '';
+            };
+          };
+        };
+        default = {
+          enable = true;
+          port = 12345;
+        };
+        defaultText = literalExpression ''
+          {
+            enable = true;
+            port = 12345;
+          }
+        '';
+        description = mdDoc ''
+          Open the firewall port.
+        '';
       };
 
-      serviceConfig = {
-        User = "root";
-        ExecStartPre = "${lib.getExe cfg.package} validate -c /etc/dae/config.dae";
-        ExecStart = "${lib.getExe cfg.package} run --disable-timestamp -c /etc/dae/config.dae";
-        ExecReload = "${lib.getExe cfg.package} reload $MAINPID";
-        LimitNPROC = 512;
-        LimitNOFILE = 1048576;
-        Restart = "on-abnormal";
-        Type = "notify";
+      configFile = mkOption {
+        type = with types; (nullOr path);
+        default = null;
+        example = "/path/to/your/config.dae";
+        description = mdDoc ''
+          The path of dae config file, end with `.dae`.
+        '';
+      };
+
+      config = mkOption {
+        type = with types; (nullOr str);
+        default = null;
+        description = mdDoc ''
+          WARNING: This option will expose store your config unencrypted world-readable in the nix store.
+          Config text for dae.
+
+          See <https://github.com/daeuniverse/dae/blob/main/example.dae>.
+        '';
       };
 
-      wantedBy = [ "multi-user.target" ];
+      disableTxChecksumIpGeneric =
+        mkEnableOption "" // { description = mdDoc "See <https://github.com/daeuniverse/dae/issues/43>"; };
+
     };
   };
+
+  config = lib.mkIf cfg.enable
+
+    {
+      environment.systemPackages = [ cfg.package ];
+      systemd.packages = [ cfg.package ];
+
+      networking = lib.mkIf cfg.openFirewall.enable {
+        firewall =
+          let portToOpen = cfg.openFirewall.port;
+          in
+          {
+            allowedTCPPorts = [ portToOpen ];
+            allowedUDPPorts = [ portToOpen ];
+          };
+      };
+
+      systemd.services.dae =
+        let
+          daeBin = lib.getExe cfg.package;
+
+          configPath =
+            if cfg.configFile != null
+            then cfg.configFile else pkgs.writeText "config.dae" cfg.config;
+
+          TxChecksumIpGenericWorkaround = with lib;
+            (getExe pkgs.writeShellApplication {
+              name = "disable-tx-checksum-ip-generic";
+              text = with pkgs; ''
+                iface=$(${iproute2}/bin/ip route | ${lib.getExe gawk} '/default/ {print $5}')
+                ${lib.getExe ethtool} -K "$iface" tx-checksum-ip-generic off
+              '';
+            });
+        in
+        {
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            LoadCredential = [ "config.dae:${configPath}" ];
+            ExecStartPre = [ "" "${daeBin} validate -c \${CREDENTIALS_DIRECTORY}/config.dae" ]
+              ++ (with lib; optional cfg.disableTxChecksumIpGeneric TxChecksumIpGenericWorkaround);
+            ExecStart = [ "" "${daeBin} run --disable-timestamp -c \${CREDENTIALS_DIRECTORY}/config.dae" ];
+            Environment = "DAE_LOCATION_ASSET=${cfg.assetsPath}";
+          };
+        };
+
+      assertions = [
+        {
+          assertion = lib.pathExists (toString (genAssetsDrv cfg.assets) + "/share/v2ray");
+          message = ''
+            Packages in `assets` has no preset paths included.
+            Please set `assetsPath` instead.
+          '';
+        }
+
+        {
+          assertion = !((config.services.dae.config != null)
+            && (config.services.dae.configFile != null));
+          message = ''
+            Option `config` and `configFile` could not be set
+            at the same time.
+          '';
+        }
+
+        {
+          assertion = !((config.services.dae.config == null)
+            && (config.services.dae.configFile == null));
+          message = ''
+            Either `config` or `configFile` should be set.
+          '';
+        }
+      ];
+    };
 }
diff --git a/nixos/modules/services/networking/dante.nix b/nixos/modules/services/networking/dante.nix
index 605f2d74f8275..f0d1d6305c54d 100644
--- a/nixos/modules/services/networking/dante.nix
+++ b/nixos/modules/services/networking/dante.nix
@@ -47,6 +47,7 @@ in
 
     systemd.services.dante = {
       description   = "Dante SOCKS v4 and v5 compatible proxy server";
+      wants         = [ "network-online.target" ];
       after         = [ "network-online.target" ];
       wantedBy      = [ "multi-user.target" ];
 
diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix
new file mode 100644
index 0000000000000..18f205b8d99ef
--- /dev/null
+++ b/nixos/modules/services/networking/ddclient.nix
@@ -0,0 +1,234 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.ddclient;
+  boolToStr = bool: if bool then "yes" else "no";
+  dataDir = "/var/lib/ddclient";
+  StateDirectory = builtins.baseNameOf dataDir;
+  RuntimeDirectory = StateDirectory;
+
+  configFile' = pkgs.writeText "ddclient.conf" ''
+    # This file can be used as a template for configFile or is automatically generated by Nix options.
+    cache=${dataDir}/ddclient.cache
+    foreground=YES
+    use=${cfg.use}
+    login=${cfg.username}
+    password=${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}"}
+    ${lib.optionalString (cfg.zone != "")   "zone=${cfg.zone}"}
+    ssl=${boolToStr cfg.ssl}
+    wildcard=YES
+    quiet=${boolToStr cfg.quiet}
+    verbose=${boolToStr cfg.verbose}
+    ${cfg.extraConfig}
+    ${lib.concatStringsSep "," cfg.domains}
+  '';
+  configFile = if (cfg.configFile != null) then cfg.configFile else configFile';
+
+  preStart = ''
+    install --mode=600 --owner=$USER ${configFile} /run/${RuntimeDirectory}/ddclient.conf
+    ${lib.optionalString (cfg.configFile == null) (if (cfg.protocol == "nsupdate") then ''
+      install --mode=600 --owner=$USER ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
+    '' else if (cfg.passwordFile != null) then ''
+      "${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf"
+    '' else ''
+      sed -i '/^password=@password_placeholder@$/d' /run/${RuntimeDirectory}/ddclient.conf
+    '')}
+  '';
+
+in
+
+with lib;
+
+{
+
+  imports = [
+    (mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
+      (config:
+        let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
+        in optional (value != "") value))
+    (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
+    (mkRemovedOptionModule [ "services" "ddclient" "password" ] "Use services.ddclient.passwordFile instead.")
+    (mkRemovedOptionModule [ "services" "ddclient" "ipv6" ] "")
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.ddclient = with lib.types; {
+
+      enable = mkOption {
+        default = false;
+        type = bool;
+        description = lib.mdDoc ''
+          Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
+        '';
+      };
+
+      package = mkOption {
+        type = package;
+        default = pkgs.ddclient;
+        defaultText = lib.literalExpression "pkgs.ddclient";
+        description = lib.mdDoc ''
+          The ddclient executable package run by the service.
+        '';
+      };
+
+      domains = mkOption {
+        default = [ "" ];
+        type = listOf str;
+        description = lib.mdDoc ''
+          Domain name(s) to synchronize.
+        '';
+      };
+
+      username = mkOption {
+        # For `nsupdate` username contains the path to the nsupdate executable
+        default = lib.optionalString (config.services.ddclient.protocol == "nsupdate") "${pkgs.bind.dnsutils}/bin/nsupdate";
+        defaultText = "";
+        type = str;
+        description = lib.mdDoc ''
+          User name.
+        '';
+      };
+
+      passwordFile = mkOption {
+        default = null;
+        type = nullOr str;
+        description = lib.mdDoc ''
+          A file containing the password or a TSIG key in named format when using the nsupdate protocol.
+        '';
+      };
+
+      interval = mkOption {
+        default = "10min";
+        type = str;
+        description = lib.mdDoc ''
+          The interval at which to run the check and update.
+          See {command}`man 7 systemd.time` for the format.
+        '';
+      };
+
+      configFile = mkOption {
+        default = null;
+        type = nullOr path;
+        description = lib.mdDoc ''
+          Path to configuration file.
+          When set this overrides the generated configuration from module options.
+        '';
+        example = "/root/nixos/secrets/ddclient.conf";
+      };
+
+      protocol = mkOption {
+        default = "dyndns2";
+        type = str;
+        description = lib.mdDoc ''
+          Protocol to use with dynamic DNS provider (see https://ddclient.net/protocols.html ).
+        '';
+      };
+
+      server = mkOption {
+        default = "";
+        type = str;
+        description = lib.mdDoc ''
+          Server address.
+        '';
+      };
+
+      ssl = mkOption {
+        default = true;
+        type = bool;
+        description = lib.mdDoc ''
+          Whether to use SSL/TLS to connect to dynamic DNS provider.
+        '';
+      };
+
+      quiet = mkOption {
+        default = false;
+        type = bool;
+        description = lib.mdDoc ''
+          Print no messages for unnecessary updates.
+        '';
+      };
+
+      script = mkOption {
+        default = "";
+        type = str;
+        description = lib.mdDoc ''
+          script as required by some providers.
+        '';
+      };
+
+      use = mkOption {
+        default = "web, web=checkip.dyndns.com/, web-skip='Current IP Address: '";
+        type = str;
+        description = lib.mdDoc ''
+          Method to determine the IP address to send to the dynamic DNS provider.
+        '';
+      };
+
+      verbose = mkOption {
+        default = false;
+        type = bool;
+        description = lib.mdDoc ''
+          Print verbose information.
+        '';
+      };
+
+      zone = mkOption {
+        default = "";
+        type = str;
+        description = lib.mdDoc ''
+          zone as required by some providers.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = lines;
+        description = lib.mdDoc ''
+          Extra configuration. Contents will be added verbatim to the configuration file.
+
+          ::: {.note}
+          `daemon` should not be added here because it does not work great with the systemd-timer approach the service uses.
+          :::
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.ddclient.enable {
+    systemd.services.ddclient = {
+      description = "Dynamic DNS Client";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      restartTriggers = optional (cfg.configFile != null) cfg.configFile;
+      path = lib.optional (lib.hasPrefix "if," cfg.use) pkgs.iproute2;
+
+      serviceConfig = {
+        DynamicUser = true;
+        RuntimeDirectoryMode = "0700";
+        inherit RuntimeDirectory;
+        inherit StateDirectory;
+        Type = "oneshot";
+        ExecStartPre = [ "!${pkgs.writeShellScript "ddclient-prestart" preStart}" ];
+        ExecStart = "${lib.getExe cfg.package} -file /run/${RuntimeDirectory}/ddclient.conf";
+      };
+    };
+
+    systemd.timers.ddclient = {
+      description = "Run ddclient";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnBootSec = cfg.interval;
+        OnUnitInactiveSec = cfg.interval;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/deconz.nix b/nixos/modules/services/networking/deconz.nix
new file mode 100644
index 0000000000000..05b7247087771
--- /dev/null
+++ b/nixos/modules/services/networking/deconz.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.deconz;
+  name = "deconz";
+  stateDir = "/var/lib/${name}";
+  # ref. upstream deconz.service
+  capabilities =
+    lib.optionals (cfg.httpPort < 1024 || cfg.wsPort < 1024) [ "CAP_NET_BIND_SERVICE" ]
+    ++ lib.optionals (cfg.allowRebootSystem) [ "CAP_SYS_BOOT" ]
+    ++ lib.optionals (cfg.allowRestartService) [ "CAP_KILL" ]
+    ++ lib.optionals (cfg.allowSetSystemTime) [ "CAP_SYS_TIME" ];
+in
+{
+  options.services.deconz = {
+
+    enable = lib.mkEnableOption "deCONZ, a Zigbee gateway for use with ConBee hardware (https://phoscon.de/en/conbee2)";
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.deconz;
+      defaultText = lib.literalExpression "pkgs.deconz";
+      description = "Which deCONZ package to use.";
+    };
+
+    device = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = ''
+        Force deCONZ to use a specific USB device (e.g. /dev/ttyACM0). By
+        default it does a search.
+      '';
+    };
+
+    listenAddress = lib.mkOption {
+      type = lib.types.str;
+      default = "127.0.0.1";
+      description = ''
+        Pin deCONZ to the network interface specified through the provided IP
+        address. This applies for the webserver as well as the websocket
+        notifications.
+      '';
+    };
+
+    httpPort = lib.mkOption {
+      type = lib.types.port;
+      default = 80;
+      description = "TCP port for the web server.";
+    };
+
+    wsPort = lib.mkOption {
+      type = lib.types.port;
+      default = 443;
+      description = "TCP port for the WebSocket.";
+    };
+
+    openFirewall = lib.mkEnableOption "opening up the service ports in the firewall";
+
+    allowRebootSystem = lib.mkEnableOption "rebooting the system";
+
+    allowRestartService = lib.mkEnableOption "killing/restarting processes";
+
+    allowSetSystemTime = lib.mkEnableOption "setting the system time";
+
+    extraArgs = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ ];
+      example = [
+        "--dbg-info=1"
+        "--dbg-err=2"
+      ];
+      description = ''
+        Extra command line arguments for deCONZ, see
+        https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/deCONZ-command-line-parameters.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
+      cfg.httpPort
+      cfg.wsPort
+    ];
+
+    services.udev.packages = [ cfg.package ];
+
+    systemd.services.deconz = {
+      description = "deCONZ Zigbee gateway";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        # The service puts a nix store path reference in here, and that path can
+        # be garbage collected. Ensure the file gets "refreshed" on every start.
+        rm -f ${stateDir}/.local/share/dresden-elektronik/deCONZ/zcldb.txt
+      '';
+      environment = {
+        HOME = stateDir;
+        XDG_RUNTIME_DIR = "/run/${name}";
+      };
+      serviceConfig = {
+        ExecStart =
+          "${lib.getExe cfg.package}"
+          + " -platform minimal"
+          + " --http-listen=${cfg.listenAddress}"
+          + " --http-port=${toString cfg.httpPort}"
+          + " --ws-port=${toString cfg.wsPort}"
+          + " --auto-connect=1"
+          + (lib.optionalString (cfg.device != null) " --dev=${cfg.device}")
+          + " " + (lib.escapeShellArgs cfg.extraArgs);
+        Restart = "on-failure";
+        AmbientCapabilities = capabilities;
+        CapabilityBoundingSet = capabilities;
+        UMask = "0027";
+        DynamicUser = true;
+        RuntimeDirectory = name;
+        RuntimeDirectoryMode = "0700";
+        StateDirectory = name;
+        WorkingDirectory = stateDir;
+        # For access to /dev/ttyACM0 (ConBee).
+        SupplementaryGroups = [ "dialout" ];
+        ProtectHome = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index 8b6d3fc55f3e4..2b59352ac616b 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -98,7 +98,7 @@ let
           # anything ever again ("couldn't resolve ..., giving up on
           # it"), so we silently lose time synchronisation. This also
           # applies to openntpd.
-          /run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service || true
+          /run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service ntpd-rs.service || true
       fi
 
       ${cfg.runHook}
diff --git a/nixos/modules/services/networking/dnsmasq.md b/nixos/modules/services/networking/dnsmasq.md
new file mode 100644
index 0000000000000..6fc9178b1c0d5
--- /dev/null
+++ b/nixos/modules/services/networking/dnsmasq.md
@@ -0,0 +1,68 @@
+# Dnsmasq {#module-services-networking-dnsmasq}
+
+Dnsmasq is an integrated DNS, DHCP and TFTP server for small networks.
+
+## Configuration {#module-services-networking-dnsmasq-configuration}
+
+### An authoritative DHCP and DNS server on a home network {#module-services-networking-dnsmasq-configuration-home}
+
+On a home network, you can use Dnsmasq as a DHCP and DNS server. New devices on
+your network will be configured by Dnsmasq, and instructed to use it as the DNS
+server by default. This allows you to rely on your own server to perform DNS
+queries and caching, with DNSSEC enabled.
+
+The following example assumes that
+
+- you have disabled your router's integrated DHCP server, if it has one
+- your router's address is set in  [](#opt-networking.defaultGateway.address)
+- your system's Ethernet interface is `eth0`
+- you have configured the address(es) to forward DNS queries in [](#opt-networking.nameservers)
+
+```nix
+{
+  services.dnsmasq = {
+    enable = true;
+    settings = {
+      interface = "eth0";
+      bind-interfaces = true; # Only bind to the specified interface
+      dhcp-authoritative = true; # Should be set when dnsmasq is definitely the only DHCP server on a network
+
+      server = config.networking.nameservers; # Upstream dns servers to which requests should be forwarded
+
+      dhcp-host = [
+        # Give the current system a fixed address of 192.168.0.254
+        "dc:a6:32:0b:ea:b9,192.168.0.254,${config.networking.hostName},infinite"
+      ];
+
+      dhcp-option = [
+        # Address of the gateway, i.e. your router
+        "option:router,${config.networking.defaultGateway.address}"
+      ];
+
+      dhcp-range = [
+        # Range of IPv4 addresses to give out
+        # <range start>,<range end>,<lease time>
+        "192.168.0.10,192.168.0.253,24h"
+        # Enable stateless IPv6 allocation
+        "::f,::ff,constructor:eth0,ra-stateless"
+      ];
+
+      dhcp-rapid-commit = true; # Faster DHCP negotiation for IPv6
+      local-service = true; # Accept DNS queries only from hosts whose address is on a local subnet
+      log-queries = true; # Log results of all DNS queries
+      bogus-priv = true; # Don't forward requests for the local address ranges (192.168.x.x etc) to upstream nameservers
+      domain-needed = true; # Don't forward requests without dots or domain parts to upstream nameservers
+
+      dnssec = true; # Enable DNSSEC
+      # DNSSEC trust anchor. Source: https://data.iana.org/root-anchors/root-anchors.xml
+      trust-anchor = ".,20326,8,2,E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D";
+    };
+  };
+}
+```
+
+## References {#module-services-networking-dnsmasq-references}
+
+- Upstream website: <https://dnsmasq.org>
+- Manpage: <https://dnsmasq.org/docs/dnsmasq-man.html>
+- FAQ: <https://dnsmasq.org/docs/FAQ>
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 4886654e8c033..d01a1b6707a53 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.services.dnsmasq;
-  dnsmasq = pkgs.dnsmasq;
+  dnsmasq = cfg.package;
   stateDir = "/var/lib/dnsmasq";
 
   # True values are just put as `name` instead of `name=true`, and false values
@@ -53,6 +53,8 @@ in
         '';
       };
 
+      package = mkPackageOption pkgs "dnsmasq" {};
+
       resolveLocalQueries = mkOption {
         type = types.bool;
         default = true;
@@ -179,4 +181,6 @@ in
         restartTriggers = [ config.environment.etc.hosts.source ];
     };
   };
+
+  meta.doc = ./dnsmasq.md;
 }
diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix
index 3feafc3bb3bd1..78af256f9c81b 100644
--- a/nixos/modules/services/networking/ejabberd.nix
+++ b/nixos/modules/services/networking/ejabberd.nix
@@ -29,12 +29,7 @@ in {
         description = lib.mdDoc "Whether to enable ejabberd server";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.ejabberd;
-        defaultText = literalExpression "pkgs.ejabberd";
-        description = lib.mdDoc "ejabberd server package to use";
-      };
+      package = mkPackageOption pkgs "ejabberd" { };
 
       user = mkOption {
         type = types.str;
@@ -125,6 +120,12 @@ in {
         if [ -z "$(ls -A '${cfg.spoolDir}')" ]; then
           touch "${cfg.spoolDir}/.firstRun"
         fi
+
+        if ! test -e ${cfg.spoolDir}/.erlang.cookie; then
+          touch ${cfg.spoolDir}/.erlang.cookie
+          chmod 600 ${cfg.spoolDir}/.erlang.cookie
+          dd if=/dev/random bs=16 count=1 | base64 > ${cfg.spoolDir}/.erlang.cookie
+        fi
       '';
 
       postStart = ''
diff --git a/nixos/modules/services/networking/envoy.nix b/nixos/modules/services/networking/envoy.nix
index c68ceab9619c4..779c77ff6c81e 100644
--- a/nixos/modules/services/networking/envoy.nix
+++ b/nixos/modules/services/networking/envoy.nix
@@ -17,7 +17,7 @@ in
   options.services.envoy = {
     enable = mkEnableOption (lib.mdDoc "Envoy reverse proxy");
 
-    package = mkPackageOptionMD pkgs "envoy" { };
+    package = mkPackageOption pkgs "envoy" { };
 
     requireValidConfig = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/networking/epmd.nix b/nixos/modules/services/networking/epmd.nix
index 0bc8c71f4eaa3..318e325944b5d 100644
--- a/nixos/modules/services/networking/epmd.nix
+++ b/nixos/modules/services/networking/epmd.nix
@@ -17,15 +17,7 @@ in
         Erlang computations.
       '';
     };
-    package = mkOption {
-      type = types.package;
-      default = pkgs.erlang;
-      defaultText = literalExpression "pkgs.erlang";
-      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.
-      '';
-    };
+    package = mkPackageOption pkgs "erlang" { };
     listenStream = mkOption
       {
         type = types.str;
diff --git a/nixos/modules/services/networking/ergo.nix b/nixos/modules/services/networking/ergo.nix
index 033d4d9caf8a8..1bee0f43f988a 100644
--- a/nixos/modules/services/networking/ergo.nix
+++ b/nixos/modules/services/networking/ergo.nix
@@ -114,6 +114,7 @@ in {
     systemd.services.ergo = {
       description = "ergo server";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       serviceConfig = {
         User = cfg.user;
diff --git a/nixos/modules/services/networking/expressvpn.nix b/nixos/modules/services/networking/expressvpn.nix
index 30de6987d31fe..05c24d8bccffc 100644
--- a/nixos/modules/services/networking/expressvpn.nix
+++ b/nixos/modules/services/networking/expressvpn.nix
@@ -21,6 +21,7 @@ with lib;
         RestartSec = 5;
       };
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network.target" "network-online.target" ];
     };
   };
diff --git a/nixos/modules/services/networking/fastnetmon-advanced.nix b/nixos/modules/services/networking/fastnetmon-advanced.nix
new file mode 100644
index 0000000000000..26e8ad8b76d97
--- /dev/null
+++ b/nixos/modules/services/networking/fastnetmon-advanced.nix
@@ -0,0 +1,222 @@
+{ config, lib, pkgs, ... }:
+
+let
+  # Background information: FastNetMon requires a MongoDB to start. This is because
+  # it uses MongoDB to store its configuration. That is, in a normal setup there is
+  # one collection with one document.
+  # To provide declarative configuration in our NixOS module, this database is
+  # completely emptied and replaced on each boot by the fastnetmon-setup service
+  # using the configuration backup functionality.
+
+  cfg = config.services.fastnetmon-advanced;
+  settingsFormat = pkgs.formats.yaml { };
+
+  # obtain the default configs by starting up ferretdb and fcli in a derivation
+  default_configs = pkgs.runCommand "default-configs" {
+    nativeBuildInputs = [
+      pkgs.ferretdb
+      pkgs.fastnetmon-advanced # for fcli
+      pkgs.proot
+    ];
+  } ''
+    mkdir ferretdb fastnetmon $out
+    FERRETDB_TELEMETRY="disable" FERRETDB_HANDLER="sqlite" FERRETDB_STATE_DIR="$PWD/ferretdb" FERRETDB_SQLITE_URL="file:$PWD/ferretdb/" ferretdb &
+
+    cat << EOF > fastnetmon/fastnetmon.conf
+    ${builtins.toJSON {
+      mongodb_username = "";
+    }}
+    EOF
+    proot -b fastnetmon:/etc/fastnetmon -0 fcli create_configuration
+    proot -b fastnetmon:/etc/fastnetmon -0 fcli set bgp default
+    proot -b fastnetmon:/etc/fastnetmon -0 fcli export_configuration backup.tar
+    tar -C $out --no-same-owner -xvf backup.tar
+  '';
+
+  # merge the user configs into the default configs
+  config_tar = pkgs.runCommand "fastnetmon-config.tar" {
+    nativeBuildInputs = with pkgs; [ jq ];
+  } ''
+    jq -s add ${default_configs}/main.json ${pkgs.writeText "main-add.json" (builtins.toJSON cfg.settings)} > main.json
+    mkdir hostgroup
+    ${lib.concatImapStringsSep "\n" (pos: hostgroup: ''
+      jq -s add ${default_configs}/hostgroup/0.json ${pkgs.writeText "hostgroup-${toString (pos - 1)}-add.json" (builtins.toJSON hostgroup)} > hostgroup/${toString (pos - 1)}.json
+    '') hostgroups}
+    mkdir bgp
+    ${lib.concatImapStringsSep "\n" (pos: bgp: ''
+      jq -s add ${default_configs}/bgp/0.json ${pkgs.writeText "bgp-${toString (pos - 1)}-add.json" (builtins.toJSON bgp)} > bgp/${toString (pos - 1)}.json
+    '') bgpPeers}
+    tar -cf $out main.json ${lib.concatImapStringsSep " " (pos: _: "hostgroup/${toString (pos - 1)}.json") hostgroups} ${lib.concatImapStringsSep " " (pos: _: "bgp/${toString (pos - 1)}.json") bgpPeers}
+  '';
+
+  hostgroups = lib.mapAttrsToList (name: hostgroup: { inherit name; } // hostgroup) cfg.hostgroups;
+  bgpPeers = lib.mapAttrsToList (name: bgpPeer: { inherit name; } // bgpPeer) cfg.bgpPeers;
+
+in {
+  options.services.fastnetmon-advanced = with lib; {
+    enable = mkEnableOption "the fastnetmon-advanced DDoS Protection daemon";
+
+    settings = mkOption {
+      description = ''
+        Extra configuration options to declaratively load into FastNetMon Advanced.
+
+        See the [FastNetMon Advanced Configuration options reference](https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-configuration-options/) for more details.
+      '';
+      type = settingsFormat.type;
+      default = {};
+      example = literalExpression ''
+        {
+          networks_list = [ "192.0.2.0/24" ];
+          gobgp = true;
+          gobgp_flow_spec_announces = true;
+        }
+      '';
+    };
+    hostgroups = mkOption {
+      description = "Hostgroups to declaratively load into FastNetMon Advanced";
+      type = types.attrsOf settingsFormat.type;
+      default = {};
+    };
+    bgpPeers = mkOption {
+      description = "BGP Peers to declaratively load into FastNetMon Advanced";
+      type = types.attrsOf settingsFormat.type;
+      default = {};
+    };
+
+    enableAdvancedTrafficPersistence = mkOption {
+      description = "Store historical flow data in clickhouse";
+      type = types.bool;
+      default = false;
+    };
+
+    traffic_db.settings = mkOption {
+      type = settingsFormat.type;
+      description = "Additional settings for /etc/fastnetmon/traffic_db.conf";
+    };
+  };
+
+  config = lib.mkMerge [ (lib.mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [
+      fastnetmon-advanced # for fcli
+    ];
+
+    environment.etc."fastnetmon/license.lic".source = "/var/lib/fastnetmon/license.lic";
+    environment.etc."fastnetmon/gobgpd.conf".source = "/run/fastnetmon/gobgpd.conf";
+    environment.etc."fastnetmon/fastnetmon.conf".source = pkgs.writeText "fastnetmon.conf" (builtins.toJSON {
+      mongodb_username = "";
+    });
+
+    services.ferretdb.enable = true;
+
+    systemd.services.fastnetmon-setup = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "ferretdb.service" ];
+      path = with pkgs; [ fastnetmon-advanced config.systemd.package ];
+      script = ''
+        fcli create_configuration
+        fcli delete hostgroup global
+        fcli import_configuration ${config_tar}
+        systemctl --no-block try-restart fastnetmon
+      '';
+      serviceConfig.Type = "oneshot";
+    };
+
+    systemd.services.fastnetmon = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "ferretdb.service" "fastnetmon-setup.service" "polkit.service" ];
+      path = with pkgs; [ iproute2 ];
+      unitConfig = {
+        # Disable logic which shuts service when we do too many restarts
+        # We do restarts from sudo fcli commit and it's expected that we may have many restarts
+        # Details: https://github.com/systemd/systemd/issues/2416
+        StartLimitInterval = 0;
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.fastnetmon-advanced}/bin/fastnetmon --log_to_console";
+
+        LimitNOFILE = 65535;
+        # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened
+        Restart= "on-failure";
+        RestartSec= "5s";
+
+        DynamicUser = true;
+        CacheDirectory = "fastnetmon";
+        RuntimeDirectory = "fastnetmon"; # for gobgpd config
+        StateDirectory = "fastnetmon"; # for license file
+      };
+    };
+
+    security.polkit.enable = true;
+    security.polkit.extraConfig = ''
+      polkit.addRule(function(action, subject) {
+        if (action.id == "org.freedesktop.systemd1.manage-units" &&
+          subject.isInGroup("fastnetmon")) {
+          if (action.lookup("unit") == "gobgp.service") {
+            var verb = action.lookup("verb");
+            if (verb == "start" || verb == "stop" || verb == "restart") {
+              return polkit.Result.YES;
+            }
+          }
+        }
+      });
+    '';
+
+    # We don't use the existing gobgp NixOS module and package, because the gobgp
+    # version might not be compatible with fastnetmon. Also, the service name
+    # _must_ be 'gobgp' and not 'gobgpd', so that fastnetmon can reload the config.
+    systemd.services.gobgp = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = "GoBGP Routing Daemon";
+      unitConfig = {
+        ConditionPathExists = "/run/fastnetmon/gobgpd.conf";
+      };
+      serviceConfig = {
+        Type = "notify";
+        ExecStartPre = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf -d";
+        SupplementaryGroups = [ "fastnetmon" ];
+        ExecStart = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -f /run/fastnetmon/gobgpd.conf --sdnotify";
+        ExecReload = "${pkgs.fastnetmon-advanced}/bin/fnm-gobgpd -r";
+        DynamicUser = true;
+        AmbientCapabilities = "cap_net_bind_service";
+      };
+    };
+  })
+
+  (lib.mkIf (cfg.enable && cfg.enableAdvancedTrafficPersistence) {
+    ## Advanced Traffic persistence
+    ## https://fastnetmon.com/docs-fnm-advanced/fastnetmon-advanced-traffic-persistency/
+
+    services.clickhouse.enable = true;
+
+    services.fastnetmon-advanced.settings.traffic_db = true;
+
+    services.fastnetmon-advanced.traffic_db.settings = {
+      clickhouse_batch_size = lib.mkDefault 1000;
+      clickhouse_batch_delay = lib.mkDefault 1;
+      traffic_db_host = lib.mkDefault "127.0.0.1";
+      traffic_db_port = lib.mkDefault 8100;
+      clickhouse_host = lib.mkDefault "127.0.0.1";
+      clickhouse_port = lib.mkDefault 9000;
+      clickhouse_user = lib.mkDefault "default";
+      clickhouse_password = lib.mkDefault "";
+    };
+    environment.etc."fastnetmon/traffic_db.conf".text = builtins.toJSON cfg.traffic_db.settings;
+
+    systemd.services.traffic_db = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.fastnetmon-advanced}/bin/traffic_db";
+        # Restart service when it fails due to any reasons, we need to keep processing traffic no matter what happened
+        Restart= "on-failure";
+        RestartSec= "5s";
+
+        DynamicUser = true;
+      };
+    };
+
+  }) ];
+
+  meta.maintainers = lib.teams.wdz.members;
+}
diff --git a/nixos/modules/services/networking/ferm.nix b/nixos/modules/services/networking/ferm.nix
index 09151eb0b544b..5ebf7aacb4dbb 100644
--- a/nixos/modules/services/networking/ferm.nix
+++ b/nixos/modules/services/networking/ferm.nix
@@ -33,12 +33,7 @@ in {
         defaultText = literalMD "empty firewall, allows any traffic";
         type = types.lines;
       };
-      package = mkOption {
-        description = lib.mdDoc "The ferm package.";
-        type = types.package;
-        default = pkgs.ferm;
-        defaultText = literalExpression "pkgs.ferm";
-      };
+      package = mkPackageOption pkgs "ferm" { };
     };
   };
 
diff --git a/nixos/modules/services/networking/firefox-syncserver.md b/nixos/modules/services/networking/firefox-syncserver.md
index 3ee863343ece2..4d8777d204bb2 100644
--- a/nixos/modules/services/networking/firefox-syncserver.md
+++ b/nixos/modules/services/networking/firefox-syncserver.md
@@ -45,7 +45,7 @@ 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
+`enableTLS` and `hostname`. 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
diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix
index 42924d7f69938..71eb2f537acc8 100644
--- a/nixos/modules/services/networking/firefox-syncserver.nix
+++ b/nixos/modules/services/networking/firefox-syncserver.nix
@@ -224,10 +224,12 @@ in
           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>
+          See the example config in
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/config/local.example.toml>
+          and the doc comments on the `Settings` structs in
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage-settings/src/lib.rs>
           and
-          <https://github.com/mozilla-services/syncstorage-rs/blob/master/syncstorage/src/tokenserver/settings.rs>
+          <https://github.com/mozilla-services/syncstorage-rs/blob/master/tokenserver-settings/src/lib.rs>
           for available options.
         '';
       };
diff --git a/nixos/modules/services/networking/firewall-iptables.nix b/nixos/modules/services/networking/firewall-iptables.nix
index 63e952194d671..2d11517700086 100644
--- a/nixos/modules/services/networking/firewall-iptables.nix
+++ b/nixos/modules/services/networking/firewall-iptables.nix
@@ -301,14 +301,16 @@ in
       }
     ];
 
+    environment.systemPackages = [ pkgs.nixos-firewall-tool ];
     networking.firewall.checkReversePath = mkIf (!kernelHasRPFilter) (mkDefault false);
 
     systemd.services.firewall = {
       description = "Firewall";
       wantedBy = [ "sysinit.target" ];
       wants = [ "network-pre.target" ];
-      before = [ "network-pre.target" ];
       after = [ "systemd-modules-load.service" ];
+      before = [ "network-pre.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
 
       path = [ cfg.package ] ++ cfg.extraPackages;
 
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index 6ed4f78ddc92b..2c2b6dc58cce6 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -16,12 +16,7 @@ in {
   options.services.flannel = {
     enable = mkEnableOption (lib.mdDoc "flannel");
 
-    package = mkOption {
-      description = lib.mdDoc "Package to use for flannel";
-      type = types.package;
-      default = pkgs.flannel;
-      defaultText = literalExpression "pkgs.flannel";
-    };
+    package = mkPackageOption pkgs "flannel" { };
 
     publicIp = mkOption {
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/networking/frp.nix b/nixos/modules/services/networking/frp.nix
new file mode 100644
index 0000000000000..eb022308bc29f
--- /dev/null
+++ b/nixos/modules/services/networking/frp.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.frp;
+  settingsFormat = pkgs.formats.toml { };
+  configFile = settingsFormat.generate "frp.toml" cfg.settings;
+  isClient = (cfg.role == "client");
+  isServer = (cfg.role == "server");
+in
+{
+  options = {
+    services.frp = {
+      enable = mkEnableOption (mdDoc "frp");
+
+      package = mkPackageOption pkgs "frp" { };
+
+      role = mkOption {
+        type = types.enum [ "server" "client" ];
+        description = mdDoc ''
+          The frp consists of `client` and `server`. The server is usually
+          deployed on the machine with a public IP address, and
+          the client is usually deployed on the machine
+          where the Intranet service to be penetrated resides.
+        '';
+      };
+
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        description = mdDoc ''
+          Frp configuration, for configuration options
+          see the example of [client](https://github.com/fatedier/frp/blob/dev/conf/frpc_full_example.toml)
+          or [server](https://github.com/fatedier/frp/blob/dev/conf/frps_full_example.toml) on github.
+        '';
+        example = {
+            serverAddr = "x.x.x.x";
+            serverPort = 7000;
+          };
+      };
+    };
+  };
+
+  config =
+    let
+      serviceCapability = optionals isServer [ "CAP_NET_BIND_SERVICE" ];
+      executableFile = if isClient then "frpc" else "frps";
+    in
+    mkIf cfg.enable {
+      systemd.services = {
+        frp = {
+          wants = optionals isClient [ "network-online.target" ];
+          after = if isClient then [ "network-online.target" ] else [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+          description = "A fast reverse proxy frp ${cfg.role}";
+          serviceConfig = {
+            Type = "simple";
+            Restart = "on-failure";
+            RestartSec = 15;
+            ExecStart = "${cfg.package}/bin/${executableFile} --strict_config -c ${configFile}";
+            StateDirectoryMode = optionalString isServer "0700";
+            DynamicUser = true;
+            # Hardening
+            UMask = optionalString isServer "0007";
+            CapabilityBoundingSet = serviceCapability;
+            AmbientCapabilities = serviceCapability;
+            PrivateDevices = true;
+            ProtectHostname = true;
+            ProtectClock = true;
+            ProtectKernelTunables = true;
+            ProtectKernelModules = true;
+            ProtectKernelLogs = true;
+            ProtectControlGroups = true;
+            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ] ++ optionals isClient [ "AF_UNIX" ];
+            LockPersonality = true;
+            MemoryDenyWriteExecute = true;
+            RestrictRealtime = true;
+            RestrictSUIDSGID = true;
+            PrivateMounts = true;
+            SystemCallArchitectures = "native";
+            SystemCallFilter = [ "@system-service" ];
+          };
+        };
+      };
+    };
+
+  meta.maintainers = with maintainers; [ zaldnoay ];
+}
diff --git a/nixos/modules/services/networking/frr.nix b/nixos/modules/services/networking/frr.nix
index d350fe3548ae1..8488a4e4ef481 100644
--- a/nixos/modules/services/networking/frr.nix
+++ b/nixos/modules/services/networking/frr.nix
@@ -23,6 +23,7 @@ let
     "pbr"
     "bfd"
     "fabric"
+    "mgmt"
   ];
 
   allServices = services ++ [ "zebra" ];
diff --git a/nixos/modules/services/networking/ghostunnel.nix b/nixos/modules/services/networking/ghostunnel.nix
index 4902367e2a6a7..d5e2ff19ce50d 100644
--- a/nixos/modules/services/networking/ghostunnel.nix
+++ b/nixos/modules/services/networking/ghostunnel.nix
@@ -9,6 +9,7 @@ let
     mapAttrs'
     mkDefault
     mkEnableOption
+    mkPackageOption
     mkIf
     mkOption
     nameValuePair
@@ -215,12 +216,7 @@ in
   options = {
     services.ghostunnel.enable = mkEnableOption (lib.mdDoc "ghostunnel");
 
-    services.ghostunnel.package = mkOption {
-      description = lib.mdDoc "The ghostunnel package to use.";
-      type = types.package;
-      default = pkgs.ghostunnel;
-      defaultText = literalExpression "pkgs.ghostunnel";
-    };
+    services.ghostunnel.package = mkPackageOption pkgs "ghostunnel" { };
 
     services.ghostunnel.servers = mkOption {
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/networking/gns3-server.md b/nixos/modules/services/networking/gns3-server.md
new file mode 100644
index 0000000000000..9320d914fbd3a
--- /dev/null
+++ b/nixos/modules/services/networking/gns3-server.md
@@ -0,0 +1,31 @@
+# GNS3 Server {#module-services-gns3-server}
+
+[GNS3](https://www.gns3.com/), a network software emulator.
+
+## Basic Usage {#module-services-gns3-server-basic-usage}
+
+A minimal configuration looks like this:
+
+```nix
+{
+  services.gns3-server = {
+    enable = true;
+
+    auth = {
+      enable = true;
+      user = "gns3";
+      passwordFile = "/var/lib/secrets/gns3_password";
+    };
+
+    ssl = {
+      enable = true;
+      certFile = "/var/lib/gns3/ssl/cert.pem";
+      keyFile = "/var/lib/gns3/ssl/key.pem";
+    };
+
+    dynamips.enable = true;
+    ubridge.enable = true;
+    vpcs.enable = true;
+  };
+}
+```
diff --git a/nixos/modules/services/networking/gns3-server.nix b/nixos/modules/services/networking/gns3-server.nix
new file mode 100644
index 0000000000000..25583765de672
--- /dev/null
+++ b/nixos/modules/services/networking/gns3-server.nix
@@ -0,0 +1,263 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.gns3-server;
+
+  settingsFormat = pkgs.formats.ini { };
+  configFile = settingsFormat.generate "gns3-server.conf" cfg.settings;
+
+in {
+  meta = {
+    doc = ./gns3-server.md;
+    maintainers = [ lib.maintainers.anthonyroussel ];
+  };
+
+  options = {
+    services.gns3-server = {
+      enable = lib.mkEnableOption (lib.mdDoc "GNS3 Server daemon");
+
+      package = lib.mkPackageOptionMD pkgs "gns3-server" { };
+
+      auth = {
+        enable = lib.mkEnableOption (lib.mdDoc "password based HTTP authentication to access the GNS3 Server");
+
+        user = lib.mkOption {
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+          example = "gns3";
+          description = lib.mdDoc ''Username used to access the GNS3 Server.'';
+        };
+
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/run/secrets/gns3-server-password";
+          description = lib.mdDoc ''
+            A file containing the password to access the GNS3 Server.
+
+            ::: {.warning}
+            This should be a string, not a nix path, since nix paths
+            are copied into the world-readable nix store.
+            :::
+          '';
+        };
+      };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule { freeformType = settingsFormat.type; };
+        default = {};
+        example = { host = "127.0.0.1"; port = 3080; };
+        description = lib.mdDoc ''
+          The global options in `config` file in ini format.
+
+          Refer to <https://docs.gns3.com/docs/using-gns3/administration/gns3-server-configuration-file/>
+          for all available options.
+        '';
+      };
+
+      log = {
+        file = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = "/var/log/gns3/server.log";
+          description = lib.mdDoc ''Path of the file GNS3 Server should log to.'';
+        };
+
+        debug = lib.mkEnableOption (lib.mdDoc "debug logging");
+      };
+
+      ssl = {
+        enable = lib.mkEnableOption (lib.mdDoc "SSL encryption");
+
+        certFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/var/lib/gns3/ssl/server.pem";
+          description = lib.mdDoc ''
+            Path to the SSL certificate file. This certificate will
+            be offered to, and may be verified by, clients.
+          '';
+        };
+
+        keyFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/var/lib/gns3/ssl/server.key";
+          description = lib.mdDoc "Private key file for the certificate.";
+        };
+      };
+
+      dynamips = {
+        enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable Dynamips support.'');
+        package = lib.mkPackageOptionMD pkgs "dynamips" { };
+      };
+
+      ubridge = {
+        enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable uBridge support.'');
+        package = lib.mkPackageOptionMD pkgs "ubridge" { };
+      };
+
+      vpcs = {
+        enable = lib.mkEnableOption (lib.mdDoc ''Whether to enable VPCS support.'');
+        package = lib.mkPackageOptionMD pkgs "vpcs" { };
+      };
+    };
+  };
+
+  config = let
+    flags = {
+      enableDocker = config.virtualisation.docker.enable;
+      enableLibvirtd = config.virtualisation.libvirtd.enable;
+    };
+
+  in lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.ssl.enable -> cfg.ssl.certFile != null;
+        message = "Please provide a certificate to use for SSL encryption.";
+      }
+      {
+        assertion = cfg.ssl.enable -> cfg.ssl.keyFile != null;
+        message = "Please provide a private key to use for SSL encryption.";
+      }
+      {
+        assertion = cfg.auth.enable -> cfg.auth.user != null;
+        message = "Please provide a username to use for HTTP authentication.";
+      }
+      {
+        assertion = cfg.auth.enable -> cfg.auth.passwordFile != null;
+        message = "Please provide a password file to use for HTTP authentication.";
+      }
+    ];
+
+    users.groups.ubridge = lib.mkIf cfg.ubridge.enable { };
+
+    security.wrappers.ubridge = lib.mkIf cfg.ubridge.enable {
+      capabilities = "cap_net_raw,cap_net_admin=eip";
+      group = "ubridge";
+      owner = "root";
+      permissions = "u=rwx,g=rx,o=r";
+      source = lib.getExe cfg.ubridge.package;
+    };
+
+    services.gns3-server.settings = lib.mkMerge [
+      {
+        Server = {
+          appliances_path = lib.mkDefault "/var/lib/gns3/appliances";
+          configs_path = lib.mkDefault "/var/lib/gns3/configs";
+          images_path = lib.mkDefault "/var/lib/gns3/images";
+          projects_path = lib.mkDefault "/var/lib/gns3/projects";
+          symbols_path = lib.mkDefault "/var/lib/gns3/symbols";
+        };
+      }
+      (lib.mkIf (cfg.ubridge.enable) {
+        Server.ubridge_path = lib.mkDefault (lib.getExe cfg.ubridge.package);
+      })
+      (lib.mkIf (cfg.auth.enable) {
+        Server = {
+          auth = lib.mkDefault (lib.boolToString cfg.auth.enable);
+          user = lib.mkDefault cfg.auth.user;
+          password = lib.mkDefault "@AUTH_PASSWORD@";
+        };
+      })
+      (lib.mkIf (cfg.vpcs.enable) {
+        VPCS.vpcs_path = lib.mkDefault (lib.getExe cfg.vpcs.package);
+      })
+      (lib.mkIf (cfg.dynamips.enable) {
+        Dynamips.dynamips_path = lib.mkDefault (lib.getExe cfg.dynamips.package);
+      })
+    ];
+
+    systemd.services.gns3-server = let
+      commandArgs = lib.cli.toGNUCommandLineShell { } {
+        config = "/etc/gns3/gns3_server.conf";
+        pid = "/run/gns3/server.pid";
+        log = cfg.log.file;
+        ssl = cfg.ssl.enable;
+        # These are implicitly not set if `null`
+        certfile = cfg.ssl.certFile;
+        certkey = cfg.ssl.keyFile;
+      };
+    in
+    {
+      description = "GNS3 Server";
+
+      after = [ "network.target" "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+
+      # configFile cannot be stored in RuntimeDirectory, because GNS3
+      # uses the `--config` base path to stores supplementary configuration files at runtime.
+      #
+      preStart = ''
+        install -m660 ${configFile} /etc/gns3/gns3_server.conf
+
+        ${lib.optionalString cfg.auth.enable ''
+          ${pkgs.replace-secret}/bin/replace-secret \
+            '@AUTH_PASSWORD@' \
+            "''${CREDENTIALS_DIRECTORY}/AUTH_PASSWORD" \
+            /etc/gns3/gns3_server.conf
+        ''}
+      '';
+
+      path = lib.optional flags.enableLibvirtd pkgs.qemu;
+
+      reloadTriggers = [ configFile ];
+
+      serviceConfig = {
+        ConfigurationDirectory = "gns3";
+        ConfigurationDirectoryMode = "0750";
+        DynamicUser = true;
+        Environment = "HOME=%S/gns3";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStart = "${lib.getExe cfg.package} ${commandArgs}";
+        Group = "gns3";
+        LimitNOFILE = 16384;
+        LoadCredential = lib.mkIf cfg.auth.enable [ "AUTH_PASSWORD:${cfg.auth.passwordFile}" ];
+        LogsDirectory = "gns3";
+        LogsDirectoryMode = "0750";
+        PIDFile = "/run/gns3/server.pid";
+        Restart = "on-failure";
+        RestartSec = 5;
+        RuntimeDirectory = "gns3";
+        StateDirectory = "gns3";
+        StateDirectoryMode = "0750";
+        SupplementaryGroups = lib.optional flags.enableDocker "docker"
+          ++ lib.optional flags.enableLibvirtd "libvirtd"
+          ++ lib.optional cfg.ubridge.enable "ubridge";
+        User = "gns3";
+        WorkingDirectory = "%S/gns3";
+
+        # Hardening
+        DeviceAllow = lib.optional flags.enableLibvirtd "/dev/kvm";
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        # Don't restrict ProcSubset because python3Packages.psutil requires read access to /proc/stat
+        # ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+          "AF_UNIX"
+          "AF_PACKET"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/gnunet.nix b/nixos/modules/services/networking/gnunet.nix
index fdb353fd34437..a235f1605e54f 100644
--- a/nixos/modules/services/networking/gnunet.nix
+++ b/nixos/modules/services/networking/gnunet.nix
@@ -112,12 +112,8 @@ in
         };
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.gnunet;
-        defaultText = literalExpression "pkgs.gnunet";
-        description = lib.mdDoc "Overridable attribute of the gnunet package to use.";
-        example = literalExpression "pkgs.gnunet_git";
+      package = mkPackageOption pkgs "gnunet" {
+        example = "gnunet_git";
       };
 
       extraOptions = mkOption {
diff --git a/nixos/modules/services/networking/go-neb.nix b/nixos/modules/services/networking/go-neb.nix
index b65bb5f548ee8..78d24ecf17d98 100644
--- a/nixos/modules/services/networking/go-neb.nix
+++ b/nixos/modules/services/networking/go-neb.nix
@@ -9,7 +9,7 @@ let
   configFile = settingsFormat.generate "config.yaml" cfg.config;
 in {
   options.services.go-neb = {
-    enable = mkEnableOption (lib.mdDoc "Extensible matrix bot written in Go");
+    enable = mkEnableOption (lib.mdDoc "an extensible matrix bot written in Go");
 
     bindAddress = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/networking/gvpe.nix b/nixos/modules/services/networking/gvpe.nix
index 2279ceee2f58e..558f499022c81 100644
--- a/nixos/modules/services/networking/gvpe.nix
+++ b/nixos/modules/services/networking/gvpe.nix
@@ -29,7 +29,7 @@ let
 
       export PATH=$PATH:${pkgs.iproute2}/sbin
 
-      ip link set $IFNAME up
+      ip link set dev $IFNAME up
       ip address add ${cfg.ipAddress} dev $IFNAME
       ip route add ${cfg.subnet} dev $IFNAME
 
diff --git a/nixos/modules/services/networking/haproxy.nix b/nixos/modules/services/networking/haproxy.nix
index 208eb356d629d..a2f3be6c49cef 100644
--- a/nixos/modules/services/networking/haproxy.nix
+++ b/nixos/modules/services/networking/haproxy.nix
@@ -19,7 +19,7 @@ with lib;
 
       enable = mkEnableOption (lib.mdDoc "HAProxy, the reliable, high performance TCP/HTTP load balancer.");
 
-      package = mkPackageOptionMD pkgs "haproxy" { };
+      package = mkPackageOption pkgs "haproxy" { };
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/networking/harmonia.nix b/nixos/modules/services/networking/harmonia.nix
index 144fa6c708e28..b384ac9261376 100644
--- a/nixos/modules/services/networking/harmonia.nix
+++ b/nixos/modules/services/networking/harmonia.nix
@@ -14,7 +14,7 @@ in
         description = lib.mdDoc "Path to the signing key that will be used for signing the cache";
       };
 
-      package = lib.mkPackageOptionMD pkgs "harmonia" { };
+      package = lib.mkPackageOption pkgs "harmonia" { };
 
       settings = lib.mkOption {
         inherit (format) type;
@@ -28,6 +28,13 @@ in
   };
 
   config = lib.mkIf cfg.enable {
+    nix.settings.extra-allowed-users = [ "harmonia" ];
+    users.users.harmonia = {
+      isSystemUser = true;
+      group = "harmonia";
+    };
+    users.groups.harmonia = { };
+
     systemd.services.harmonia = {
       description = "harmonia binary cache service";
 
@@ -48,7 +55,7 @@ in
         ExecStart = lib.getExe cfg.package;
         User = "harmonia";
         Group = "harmonia";
-        DynamicUser = true;
+        Restart = "on-failure";
         PrivateUsers = true;
         DeviceAllow = [ "" ];
         UMask = "0066";
diff --git a/nixos/modules/services/networking/headscale.nix b/nixos/modules/services/networking/headscale.nix
index 03e6f86af53f2..95b5fcf6ebde8 100644
--- a/nixos/modules/services/networking/headscale.nix
+++ b/nixos/modules/services/networking/headscale.nix
@@ -17,14 +17,7 @@ in {
     services.headscale = {
       enable = mkEnableOption (lib.mdDoc "headscale, Open Source coordination server for Tailscale");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.headscale;
-        defaultText = literalExpression "pkgs.headscale";
-        description = lib.mdDoc ''
-          Which headscale package to use for the running server.
-        '';
-      };
+      package = mkPackageOption pkgs "headscale" { };
 
       user = mkOption {
         default = "headscale";
@@ -467,6 +460,7 @@ in {
 
     systemd.services.headscale = {
       description = "headscale coordination server for Tailscale";
+      wants = [ "network-online.target" ];
       after = ["network-online.target"];
       wantedBy = ["multi-user.target"];
       restartTriggers = [configFile];
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 4ec066c2ec970..5bd8e1d4d7a0f 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -116,10 +116,10 @@ in {
   options = {
     services.hostapd = {
       enable = mkEnableOption (mdDoc ''
-        Whether to enable hostapd. hostapd is a user space daemon for access point and
+        hostapd, a user space daemon for access point and
         authentication servers. It implements IEEE 802.11 access point management,
         IEEE 802.1X/WPA/WPA2/EAP Authenticators, RADIUS client, EAP server, and RADIUS
-        authentication server.
+        authentication server
       '');
 
       package = mkPackageOption pkgs "hostapd" {};
@@ -899,25 +899,6 @@ in {
                       '';
                     };
                   };
-
-                  managementFrameProtection = mkOption {
-                    default = "required";
-                    type = types.enum ["disabled" "optional" "required"];
-                    apply = x:
-                      getAttr x {
-                        "disabled" = 0;
-                        "optional" = 1;
-                        "required" = 2;
-                      };
-                    description = mdDoc ''
-                      Management frame protection (MFP) authenticates management frames
-                      to prevent deauthentication (or related) attacks.
-
-                      - {var}`"disabled"`: No management frame protection
-                      - {var}`"optional"`: Use MFP if a connection allows it
-                      - {var}`"required"`: Force MFP for all clients
-                    '';
-                  };
                 };
 
                 config = let
@@ -943,7 +924,8 @@ in {
 
                     # IEEE 802.11i (authentication) related configuration
                     # Encrypt management frames to protect against deauthentication and similar attacks
-                    ieee80211w = bssCfg.managementFrameProtection;
+                    ieee80211w = mkDefault 1;
+                    sae_require_mfp = mkDefault 1;
 
                     # Only allow WPA by default and disable insecure WEP
                     auth_algs = mkDefault 1;
@@ -1185,14 +1167,6 @@ in {
                   message = ''hostapd radio ${radio} bss ${bss}: bssid must be specified manually (for now) since this radio uses multiple BSS.'';
                 }
                 {
-                  assertion = auth.mode == "wpa3-sae" -> bssCfg.managementFrameProtection == 2;
-                  message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE which requires managementFrameProtection="required"'';
-                }
-                {
-                  assertion = auth.mode == "wpa3-sae-transition" -> bssCfg.managementFrameProtection != 0;
-                  message = ''hostapd radio ${radio} bss ${bss}: uses WPA3-SAE in transition mode with WPA2-SHA256, which requires managementFrameProtection="optional" or ="required"'';
-                }
-                {
                   assertion = countWpaPasswordDefinitions <= 1;
                   message = ''hostapd radio ${radio} bss ${bss}: must use at most one WPA password option (wpaPassword, wpaPasswordFile, wpaPskFile)'';
                 }
diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix
index 82c144236f3b8..49b2bef90a5fe 100644
--- a/nixos/modules/services/networking/hylafax/options.nix
+++ b/nixos/modules/services/networking/hylafax/options.nix
@@ -272,18 +272,18 @@ in
     };
 
     faxcron.enable.spoolInit = mkEnableOption (lib.mdDoc ''
-      Purge old files from the spooling area with
+      purging old files from the spooling area with
       {file}`faxcron`
-      each time the spooling area is initialized.
+      each time the spooling area is initialized
     '');
     faxcron.enable.frequency = mkOption {
       type = nullOr nonEmptyStr;
       default = null;
       example = "daily";
       description = lib.mdDoc ''
-        Purge old files from the spooling area with
+        purging old files from the spooling area with
         {file}`faxcron` with the given frequency
-        (see systemd.time(7)).
+        (see systemd.time(7))
       '';
     };
     faxcron.infoDays = mkOption {
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index c940324ad0964..8d9eff61488ca 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -239,19 +239,12 @@ in
       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
+          Please read <https://i2pd.readthedocs.io/en/latest/> for further
           configuration help.
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.i2pd;
-        defaultText = literalExpression "pkgs.i2pd";
-        description = lib.mdDoc ''
-          i2pd package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "i2pd" { };
 
       logLevel = mkOption {
         type = types.enum ["debug" "info" "warn" "error"];
@@ -265,7 +258,7 @@ in
         '';
       };
 
-      logCLFTime = mkEnableOption (lib.mdDoc "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;
@@ -456,7 +449,7 @@ in
         '';
       };
 
-      trust.enable = mkEnableOption (lib.mdDoc "Explicit trust options");
+      trust.enable = mkEnableOption (lib.mdDoc "explicit trust options");
 
       trust.family = mkOption {
         type = with types; nullOr str;
@@ -474,7 +467,7 @@ in
         '';
       };
 
-      trust.hidden = mkEnableOption (lib.mdDoc "Router concealment");
+      trust.hidden = mkEnableOption (lib.mdDoc "router concealment");
 
       websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
 
@@ -552,7 +545,7 @@ in
 
       proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
 
-        auth = mkEnableOption (lib.mdDoc "Webconsole authentication");
+        auth = mkEnableOption (lib.mdDoc "webconsole authentication");
 
         user = mkOption {
           type = types.str;
diff --git a/nixos/modules/services/networking/icecream/daemon.nix b/nixos/modules/services/networking/icecream/daemon.nix
index fdd7a139c2fad..48363cc22c367 100644
--- a/nixos/modules/services/networking/icecream/daemon.nix
+++ b/nixos/modules/services/networking/icecream/daemon.nix
@@ -99,12 +99,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        default = pkgs.icecream;
-        defaultText = literalExpression "pkgs.icecream";
-        type = types.package;
-        description = lib.mdDoc "Icecream package to use.";
-      };
+      package = mkPackageOption pkgs "icecream" { };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
diff --git a/nixos/modules/services/networking/icecream/scheduler.nix b/nixos/modules/services/networking/icecream/scheduler.nix
index 33aee1bb19cc1..2d53282ba88f7 100644
--- a/nixos/modules/services/networking/icecream/scheduler.nix
+++ b/nixos/modules/services/networking/icecream/scheduler.nix
@@ -54,12 +54,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        default = pkgs.icecream;
-        defaultText = literalExpression "pkgs.icecream";
-        type = types.package;
-        description = lib.mdDoc "Icecream package to use.";
-      };
+      package = mkPackageOption pkgs "icecream" { };
 
       extraArgs = mkOption {
         type = types.listOf types.str;
diff --git a/nixos/modules/services/networking/ircd-hybrid/builder.sh b/nixos/modules/services/networking/ircd-hybrid/builder.sh
index d9d2e4264dfd1..07a3788abf7d2 100644
--- a/nixos/modules/services/networking/ircd-hybrid/builder.sh
+++ b/nixos/modules/services/networking/ircd-hybrid/builder.sh
@@ -1,4 +1,4 @@
-if [ -e .attrs.sh ]; then source .attrs.sh; fi
+if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; elif [ -f .attrs.sh ]; then . .attrs.sh; fi
 source $stdenv/setup
 
 doSub() {
diff --git a/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixos/modules/services/networking/ircd-hybrid/default.nix
index 554b0f7bb8b44..64a34cc52d25a 100644
--- a/nixos/modules/services/networking/ircd-hybrid/default.nix
+++ b/nixos/modules/services/networking/ircd-hybrid/default.nix
@@ -125,7 +125,8 @@ in
 
     systemd.services.ircd-hybrid = {
       description = "IRCD Hybrid server";
-      after = [ "started networking" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       script = "${ircdService}/bin/control start";
     };
diff --git a/nixos/modules/services/networking/iscsi/initiator.nix b/nixos/modules/services/networking/iscsi/initiator.nix
index d2865a660ead0..2d802d8cfc709 100644
--- a/nixos/modules/services/networking/iscsi/initiator.nix
+++ b/nixos/modules/services/networking/iscsi/initiator.nix
@@ -7,7 +7,7 @@ in
     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.
+      You probably do not want this
     '');
     discoverPortal = mkOption {
       type = nullOr str;
@@ -19,12 +19,7 @@ in
       description = lib.mdDoc "Name of this iscsi initiator";
       example = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
     };
-    package = mkOption {
-      type = package;
-      description = lib.mdDoc "openiscsi package to use";
-      default = pkgs.openiscsi;
-      defaultText = literalExpression "pkgs.openiscsi";
-    };
+    package = mkPackageOption pkgs "openiscsi" { };
 
     extraConfig = mkOption {
       type = str;
@@ -52,25 +47,27 @@ in
     '';
     environment.etc."iscsi/initiatorname.iscsi".text = "InitiatorName=${cfg.name}";
 
-    system.activationScripts.iscsid = let
-      extraCfgDumper = optionalString (cfg.extraConfigFile != null) ''
-        if [ -f "${cfg.extraConfigFile}" ]; then
-          printf "\n# The following is from ${cfg.extraConfigFile}:\n"
-          cat "${cfg.extraConfigFile}"
-        else
-          echo "Warning: services.openiscsi.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2
-        fi
-      '';
-    in ''
-      (
-        cat ${config.environment.etc."iscsi/iscsid.conf.fragment".source}
-        ${extraCfgDumper}
-      ) > /etc/iscsi/iscsid.conf
-    '';
-
     systemd.packages = [ cfg.package ];
 
-    systemd.services."iscsid".wantedBy = [ "multi-user.target" ];
+    systemd.services."iscsid" = {
+      wantedBy = [ "multi-user.target" ];
+      preStart =
+        let
+          extraCfgDumper = optionalString (cfg.extraConfigFile != null) ''
+            if [ -f "${cfg.extraConfigFile}" ]; then
+              printf "\n# The following is from ${cfg.extraConfigFile}:\n"
+              cat "${cfg.extraConfigFile}"
+            else
+              echo "Warning: services.openiscsi.extraConfigFile ${cfg.extraConfigFile} does not exist!" >&2
+            fi
+          '';
+        in ''
+          (
+            cat ${config.environment.etc."iscsi/iscsid.conf.fragment".source}
+            ${extraCfgDumper}
+          ) > /etc/iscsi/iscsid.conf
+        '';
+    };
     systemd.sockets."iscsid".wantedBy = [ "sockets.target" ];
 
     systemd.services."iscsi" = mkIf cfg.enableAutoLoginOut {
diff --git a/nixos/modules/services/networking/ivpn.nix b/nixos/modules/services/networking/ivpn.nix
index 6df630c1f1947..6c9ae599e670f 100644
--- a/nixos/modules/services/networking/ivpn.nix
+++ b/nixos/modules/services/networking/ivpn.nix
@@ -27,7 +27,7 @@ with lib;
     systemd.services.ivpn-service = {
       description = "iVPN daemon";
       wantedBy = [ "multi-user.target" ];
-      wants = [ "network.target" ];
+      wants = [ "network.target" "network-online.target" ];
       after = [
         "network-online.target"
         "NetworkManager.service"
diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix
index 993a603c1ed5c..d46c1a69a6197 100644
--- a/nixos/modules/services/networking/iwd.nix
+++ b/nixos/modules/services/networking/iwd.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib)
-    mkEnableOption mkIf mkOption types
+    mkEnableOption mkPackageOption mkIf mkOption types
     recursiveUpdate;
 
   cfg = config.networking.wireless.iwd;
@@ -19,14 +19,7 @@ in
   options.networking.wireless.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.
-      '';
-    };
+    package = mkPackageOption pkgs "iwd" { };
 
     settings = mkOption {
       type = ini.type;
@@ -71,8 +64,10 @@ in
     };
 
     systemd.services.iwd = {
+      path = [ config.networking.resolvconf.package ];
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ configFile ];
+      serviceConfig.ReadWritePaths = "-/etc/resolv.conf";
     };
   };
 
diff --git a/nixos/modules/services/networking/jigasi.nix b/nixos/modules/services/networking/jigasi.nix
new file mode 100644
index 0000000000000..e701689031b14
--- /dev/null
+++ b/nixos/modules/services/networking/jigasi.nix
@@ -0,0 +1,237 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jigasi;
+  homeDirName = "jigasi-home";
+  stateDir = "/tmp";
+  sipCommunicatorPropertiesFile = "${stateDir}/${homeDirName}/sip-communicator.properties";
+  sipCommunicatorPropertiesFileUnsubstituted = "${pkgs.jigasi}/etc/jitsi/jigasi/sip-communicator.properties";
+in
+{
+  options.services.jigasi = with types; {
+    enable = mkEnableOption "Jitsi Gateway to SIP - component of Jitsi Meet";
+
+    xmppHost = mkOption {
+      type = str;
+      example = "localhost";
+      description = ''
+        Hostname of the XMPP server to connect to.
+      '';
+    };
+
+    xmppDomain = mkOption {
+      type = nullOr str;
+      example = "meet.example.org";
+      description = ''
+        Domain name of the XMMP server to which to connect as a component.
+
+        If null, <option>xmppHost</option> is used.
+      '';
+    };
+
+    componentPasswordFile = mkOption {
+      type = str;
+      example = "/run/keys/jigasi-component";
+      description = ''
+        Path to file containing component secret.
+      '';
+    };
+
+    userName = mkOption {
+      type = str;
+      default = "callcontrol";
+      description = ''
+        User part of the JID for XMPP user connection.
+      '';
+    };
+
+    userDomain = mkOption {
+      type = str;
+      example = "internal.meet.example.org";
+      description = ''
+        Domain part of the JID for XMPP user connection.
+      '';
+    };
+
+    userPasswordFile = mkOption {
+      type = str;
+      example = "/run/keys/jigasi-user";
+      description = ''
+        Path to file containing password for XMPP user connection.
+      '';
+    };
+
+    bridgeMuc = mkOption {
+      type = str;
+      example = "jigasibrewery@internal.meet.example.org";
+      description = ''
+        JID of the internal MUC used to communicate with Videobridges.
+      '';
+    };
+
+    defaultJvbRoomName = mkOption {
+      type = str;
+      default = "";
+      example = "siptest";
+      description = ''
+        Name of the default JVB room that will be joined if no special header is included in SIP invite.
+      '';
+    };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        File containing environment variables to be passed to the jigasi service,
+        in which secret tokens can be specified securely by defining values for
+        <literal>JIGASI_SIPUSER</literal>,
+        <literal>JIGASI_SIPPWD</literal>,
+        <literal>JIGASI_SIPSERVER</literal> and
+        <literal>JIGASI_SIPPORT</literal>.
+      '';
+    };
+
+    config = mkOption {
+      type = attrsOf str;
+      default = { };
+      example = literalExpression ''
+        {
+          "org.jitsi.jigasi.auth.URL" = "XMPP:jitsi-meet.example.com";
+        }
+      '';
+      description = ''
+        Contents of the <filename>sip-communicator.properties</filename> configuration file for jigasi.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.jicofo.config = {
+      "org.jitsi.jicofo.jigasi.BREWERY" = "${cfg.bridgeMuc}";
+    };
+
+    services.jigasi.config = mapAttrs (_: v: mkDefault v) {
+      "org.jitsi.jigasi.BRIDGE_MUC" = cfg.bridgeMuc;
+    };
+
+    users.groups.jitsi-meet = {};
+
+    systemd.services.jigasi = let
+      jigasiProps = {
+        "-Dnet.java.sip.communicator.SC_HOME_DIR_LOCATION" = "${stateDir}";
+        "-Dnet.java.sip.communicator.SC_HOME_DIR_NAME" = "${homeDirName}";
+        "-Djava.util.logging.config.file" = "${pkgs.jigasi}/etc/jitsi/jigasi/logging.properties";
+      };
+    in
+    {
+      description = "Jitsi Gateway to SIP";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        [ -f "${sipCommunicatorPropertiesFile}" ] && rm -f "${sipCommunicatorPropertiesFile}"
+        mkdir -p "$(dirname ${sipCommunicatorPropertiesFile})"
+        temp="${sipCommunicatorPropertiesFile}.unsubstituted"
+
+        export DOMAIN_BASE="${cfg.xmppDomain}"
+        export JIGASI_XMPP_PASSWORD=$(cat "${cfg.userPasswordFile}")
+        export JIGASI_DEFAULT_JVB_ROOM_NAME="${cfg.defaultJvbRoomName}"
+
+        # encode the credentials to base64
+        export JIGASI_SIPPWD=$(echo -n "$JIGASI_SIPPWD" | base64 -w 0)
+        export JIGASI_XMPP_PASSWORD_BASE64=$(cat "${cfg.userPasswordFile}" | base64 -w 0)
+
+        cp "${sipCommunicatorPropertiesFileUnsubstituted}" "$temp"
+        chmod 644 "$temp"
+        cat <<EOF >>"$temp"
+        net.java.sip.communicator.impl.protocol.sip.acc1403273890647.SERVER_PORT=$JIGASI_SIPPORT
+        net.java.sip.communicator.impl.protocol.sip.acc1403273890647.PREFERRED_TRANSPORT=udp
+        EOF
+        chmod 444 "$temp"
+
+        # Replace <<$VAR_NAME>> from example config to $VAR_NAME for environment substitution
+        sed -i -E \
+          's/<<([^>]+)>>/\$\1/g' \
+          "$temp"
+
+        sed -i \
+          's|\(net\.java\.sip\.communicator\.impl\.protocol\.jabber\.acc-xmpp-1\.PASSWORD=\).*|\1\$JIGASI_XMPP_PASSWORD_BASE64|g' \
+          "$temp"
+
+        sed -i \
+          's|\(#\)\(org.jitsi.jigasi.DEFAULT_JVB_ROOM_NAME=\).*|\2\$JIGASI_DEFAULT_JVB_ROOM_NAME|g' \
+          "$temp"
+
+        ${pkgs.envsubst}/bin/envsubst \
+          -o "${sipCommunicatorPropertiesFile}" \
+          -i "$temp"
+
+        # Set the brewery room name
+        sed -i \
+          's|\(net\.java\.sip\.communicator\.impl\.protocol\.jabber\.acc-xmpp-1\.BREWERY=\).*|\1${cfg.bridgeMuc}|g' \
+          "${sipCommunicatorPropertiesFile}"
+        sed -i \
+          's|\(org\.jitsi\.jigasi\.ALLOWED_JID=\).*|\1${cfg.bridgeMuc}|g' \
+          "${sipCommunicatorPropertiesFile}"
+
+
+        # Disable certificate verification for self-signed certificates
+        sed -i \
+          's|\(# \)\(net.java.sip.communicator.service.gui.ALWAYS_TRUST_MODE_ENABLED=true\)|\2|g' \
+          "${sipCommunicatorPropertiesFile}"
+      '';
+
+      restartTriggers = [
+        config.environment.etc."jitsi/jigasi/sip-communicator.properties".source
+      ];
+      environment.JAVA_SYS_PROPS = concatStringsSep " " (mapAttrsToList (k: v: "${k}=${toString v}") jigasiProps);
+
+      script = ''
+        ${pkgs.jigasi}/bin/jigasi \
+          --host="${cfg.xmppHost}" \
+          --domain="${if cfg.xmppDomain == null then cfg.xmppHost else cfg.xmppDomain}" \
+          --secret="$(cat ${cfg.componentPasswordFile})" \
+          --user_name="${cfg.userName}" \
+          --user_domain="${cfg.userDomain}" \
+          --user_password="$(cat ${cfg.userPasswordFile})" \
+          --configdir="${stateDir}" \
+          --configdirname="${homeDirName}"
+      '';
+
+      serviceConfig = {
+        Type = "exec";
+
+        DynamicUser = true;
+        User = "jigasi";
+        Group = "jitsi-meet";
+
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        StateDirectory = baseNameOf stateDir;
+        EnvironmentFile = cfg.environmentFile;
+      };
+    };
+
+    environment.etc."jitsi/jigasi/sip-communicator.properties".source =
+      mkDefault "${sipCommunicatorPropertiesFile}";
+    environment.etc."jitsi/jigasi/logging.properties".source =
+      mkDefault "${stateDir}/logging.properties-journal";
+  };
+
+  meta.maintainers = lib.teams.jitsi.members;
+}
diff --git a/nixos/modules/services/networking/jool.nix b/nixos/modules/services/networking/jool.nix
index 3aafbe40967ce..d2d2b0956e8aa 100644
--- a/nixos/modules/services/networking/jool.nix
+++ b/nixos/modules/services/networking/jool.nix
@@ -16,7 +16,7 @@ let
     TemporaryFileSystem = [ "/" ];
     BindReadOnlyPaths = [
       builtins.storeDir
-      "/run/current-system/kernel-modules"
+      "/run/booted-system/kernel-modules"
     ];
 
     # Give capabilities to load the module and configure it
@@ -31,26 +31,96 @@ let
 
   configFormat = pkgs.formats.json {};
 
-  mkDefaultAttrs = lib.mapAttrs (n: v: lib.mkDefault v);
+  # Generate the config file of instance `name`
+  nat64Conf = name:
+    configFormat.generate "jool-nat64-${name}.conf"
+      (cfg.nat64.${name} // { instance = name; });
+  siitConf = name:
+    configFormat.generate "jool-siit-${name}.conf"
+      (cfg.siit.${name} // { instance = name; });
+
+  # NAT64 config type
+  nat64Options = lib.types.submodule {
+    # The format is plain JSON
+    freeformType = configFormat.type;
+    # Some options with a default value
+    options.framework = lib.mkOption {
+      type = lib.types.enum [ "netfilter" "iptables" ];
+      default = "netfilter";
+      description = lib.mdDoc ''
+        The framework to use for attaching Jool's translation to the exist
+        kernel packet processing rules. See the
+        [documentation](https://nicmx.github.io/Jool/en/intro-jool.html#design)
+        for the differences between the two options.
+      '';
+    };
+    options.global.pool6 = lib.mkOption {
+      type = lib.types.strMatching "[[:xdigit:]:]+/[[:digit:]]+"
+        // { description = "Network prefix in CIDR notation"; };
+      default = "64:ff9b::/96";
+      description = lib.mdDoc ''
+        The prefix used for embedding IPv4 into IPv6 addresses.
+        Defaults to the well-known NAT64 prefix, defined by
+        [RFC 6052](https://datatracker.ietf.org/doc/html/rfc6052).
+      '';
+    };
+  };
 
-  defaultNat64 = {
-    instance = "default";
-    framework = "netfilter";
-    global.pool6 = "64:ff9b::/96";
+  # SIIT config type
+  siitOptions = lib.types.submodule {
+    # The format is, again, plain JSON
+    freeformType = configFormat.type;
+    # Some options with a default value
+    options = { inherit (nat64Options.getSubOptions []) framework; };
+  };
+
+  makeNat64Unit = name: opts: {
+    "jool-nat64-${name}" = {
+      description = "Jool, NAT64 setup of instance ${name}";
+      documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStartPre = "${pkgs.kmod}/bin/modprobe jool";
+        ExecStart    = "${jool-cli}/bin/jool file handle ${nat64Conf name}";
+        ExecStop     = "${jool-cli}/bin/jool -f ${nat64Conf name} instance remove";
+      } // hardening;
+    };
   };
-  defaultSiit = {
-    instance = "default";
-    framework = "netfilter";
+
+  makeSiitUnit = name: opts: {
+    "jool-siit-${name}" = {
+      description = "Jool, SIIT setup of instance ${name}";
+      documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStartPre = "${pkgs.kmod}/bin/modprobe jool_siit";
+        ExecStart    = "${jool-cli}/bin/jool_siit file handle ${siitConf name}";
+        ExecStop     = "${jool-cli}/bin/jool_siit -f ${siitConf name} instance remove";
+      } // hardening;
+    };
   };
 
-  nat64Conf = configFormat.generate "jool-nat64.conf" cfg.nat64.config;
-  siitConf  = configFormat.generate "jool-siit.conf" cfg.siit.config;
+  checkNat64 = name: _: ''
+    printf 'Validating Jool configuration for NAT64 instance "${name}"... '
+    jool file check ${nat64Conf name}
+    printf 'Ok.\n'; touch "$out"
+  '';
+
+  checkSiit = name: _: ''
+    printf 'Validating Jool configuration for SIIT instance "${name}"... '
+    jool_siit file check ${siitConf name}
+    printf 'Ok.\n'; touch "$out"
+  '';
 
 in
 
 {
-  ###### interface
-
   options = {
     networking.jool.enable = lib.mkOption {
       type = lib.types.bool;
@@ -64,157 +134,146 @@ in
         NAT64, analogous to the IPv4 NAPT. Refer to the upstream
         [documentation](https://nicmx.github.io/Jool/en/intro-xlat.html) for
         the supported modes of translation and how to configure them.
+
+        Enabling this option will install the Jool kernel module and the
+        command line tools for controlling it.
       '';
     };
 
-    networking.jool.nat64.enable = lib.mkEnableOption (lib.mdDoc "a NAT64 instance of Jool.");
-    networking.jool.nat64.config = lib.mkOption {
-      type = configFormat.type;
-      default = defaultNat64;
+    networking.jool.nat64 = lib.mkOption {
+      type = lib.types.attrsOf nat64Options;
+      default = { };
       example = lib.literalExpression ''
         {
-          # custom NAT64 prefix
-          global.pool6 = "2001:db8:64::/96";
-
-          # Port forwarding
-          bib = [
-            { # SSH 192.0.2.16 → 2001:db8:a::1
-              "protocol"     = "TCP";
-              "ipv4 address" = "192.0.2.16#22";
-              "ipv6 address" = "2001:db8:a::1#22";
-            }
-            { # DNS (TCP) 192.0.2.16 → 2001:db8:a::2
-              "protocol"     = "TCP";
-              "ipv4 address" = "192.0.2.16#53";
-              "ipv6 address" = "2001:db8:a::2#53";
-            }
-            { # DNS (UDP) 192.0.2.16 → 2001:db8:a::2
-              "protocol" = "UDP";
-              "ipv4 address" = "192.0.2.16#53";
-              "ipv6 address" = "2001:db8:a::2#53";
-            }
-          ];
-
-          pool4 = [
-            # Ports for dynamic translation
-            { protocol =  "TCP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
-            { protocol =  "UDP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
-            { protocol = "ICMP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
-
-            # Ports for static BIB entries
-            { protocol =  "TCP";  prefix = "192.0.2.16/32"; "port range" = "22"; }
-            { protocol =  "UDP";  prefix = "192.0.2.16/32"; "port range" = "53"; }
-          ];
+          default = {
+            # custom NAT64 prefix
+            global.pool6 = "2001:db8:64::/96";
+
+            # Port forwarding
+            bib = [
+              { # SSH 192.0.2.16 → 2001:db8:a::1
+                "protocol"     = "TCP";
+                "ipv4 address" = "192.0.2.16#22";
+                "ipv6 address" = "2001:db8:a::1#22";
+              }
+              { # DNS (TCP) 192.0.2.16 → 2001:db8:a::2
+                "protocol"     = "TCP";
+                "ipv4 address" = "192.0.2.16#53";
+                "ipv6 address" = "2001:db8:a::2#53";
+              }
+              { # DNS (UDP) 192.0.2.16 → 2001:db8:a::2
+                "protocol" = "UDP";
+                "ipv4 address" = "192.0.2.16#53";
+                "ipv6 address" = "2001:db8:a::2#53";
+              }
+            ];
+
+            pool4 = [
+              # Port ranges for dynamic translation
+              { protocol =  "TCP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
+              { protocol =  "UDP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
+              { protocol = "ICMP";  prefix = "192.0.2.16/32"; "port range" = "40001-65535"; }
+
+              # Ports for static BIB entries
+              { protocol =  "TCP";  prefix = "192.0.2.16/32"; "port range" = "22"; }
+              { protocol =  "UDP";  prefix = "192.0.2.16/32"; "port range" = "53"; }
+            ];
+          };
         }
       '';
       description = lib.mdDoc ''
-        The configuration of a stateful NAT64 instance of Jool managed through
-        NixOS. See https://nicmx.github.io/Jool/en/config-atomic.html for the
-        available options.
+        Definitions of NAT64 instances of Jool.
+        See the
+        [documentation](https://nicmx.github.io/Jool/en/config-atomic.html) for
+        the available options. Also check out the
+        [tutorial](https://nicmx.github.io/Jool/en/run-nat64.html) for an
+        introduction to NAT64 and how to troubleshoot the setup.
+
+        The attribute name defines the name of the instance, with the main one
+        being `default`: this can be accessed from the command line without
+        specifying the name with `-i`.
 
         ::: {.note}
-        Existing or more instances created manually will not interfere with the
-        NixOS instance, provided the respective `pool4` addresses and port
-        ranges are not overlapping.
+        Instances created imperatively from the command line will not interfere
+        with the NixOS instances, provided the respective `pool4` addresses and
+        port ranges are not overlapping.
         :::
 
         ::: {.warning}
-        Changes to the NixOS instance performed via `jool instance nixos-nat64`
-        are applied correctly but will be lost after restarting
-        `jool-nat64.service`.
+        Changes to an instance performed via `jool -i <name>` are applied
+        correctly but will be lost after restarting the respective
+        `jool-nat64-<name>.service`.
         :::
       '';
     };
 
-    networking.jool.siit.enable = lib.mkEnableOption (lib.mdDoc "a SIIT instance of Jool.");
-    networking.jool.siit.config = lib.mkOption {
-      type = configFormat.type;
-      default = defaultSiit;
+    networking.jool.siit = lib.mkOption {
+      type = lib.types.attrsOf siitOptions;
+      default = { };
       example = lib.literalExpression ''
         {
-          # Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v.
-          pool6 = "2001:db8::/96";
-
-          # Explicit address mappings
-          eamt = [
-            # 2001:db8:1:: ←→ 192.0.2.0
-            { "ipv6 prefix": "2001:db8:1::/128", "ipv4 prefix": "192.0.2.0" }
-            # 2001:db8:1::x ←→ 198.51.100.x
-            { "ipv6 prefix": "2001:db8:2::/120", "ipv4 prefix": "198.51.100.0/24" }
-          ]
+          default = {
+            # Maps any IPv4 address x.y.z.t to 2001:db8::x.y.z.t and v.v.
+            global.pool6 = "2001:db8::/96";
+
+            # Explicit address mappings
+            eamt = [
+              # 2001:db8:1:: ←→ 192.0.2.0
+              { "ipv6 prefix" = "2001:db8:1::/128"; "ipv4 prefix" = "192.0.2.0"; }
+              # 2001:db8:1::x ←→ 198.51.100.x
+              { "ipv6 prefix" = "2001:db8:2::/120"; "ipv4 prefix" = "198.51.100.0/24"; }
+            ];
+          };
         }
       '';
       description = lib.mdDoc ''
-        The configuration of a SIIT instance of Jool managed through
-        NixOS. See https://nicmx.github.io/Jool/en/config-atomic.html for the
-        available options.
+        Definitions of SIIT instances of Jool.
+        See the
+        [documentation](https://nicmx.github.io/Jool/en/config-atomic.html) for
+        the available options. Also check out the
+        [tutorial](https://nicmx.github.io/Jool/en/run-vanilla.html) for an
+        introduction to SIIT and how to troubleshoot the setup.
+
+        The attribute name defines the name of the instance, with the main one
+        being `default`: this can be accessed from the command line without
+        specifying the name with `-i`.
 
         ::: {.note}
-        Existing or more instances created manually will not interfere with the
-        NixOS instance, provided the respective `EAMT` address mappings are not
-        overlapping.
+        Instances created imperatively from the command line will not interfere
+        with the NixOS instances, provided the respective EAMT addresses and
+        port ranges are not overlapping.
         :::
 
         ::: {.warning}
-        Changes to the NixOS instance performed via `jool instance nixos-siit`
-        are applied correctly but will be lost after restarting
-        `jool-siit.service`.
+        Changes to an instance performed via `jool -i <name>` are applied
+        correctly but will be lost after restarting the respective
+        `jool-siit-<name>.service`.
         :::
       '';
     };
 
   };
 
-  ###### implementation
-
   config = lib.mkIf cfg.enable {
-    environment.systemPackages = [ jool-cli ];
+    # Install kernel module and cli tools
     boot.extraModulePackages = [ jool ];
+    environment.systemPackages = [ jool-cli ];
 
-    systemd.services.jool-nat64 = lib.mkIf cfg.nat64.enable {
-      description = "Jool, NAT64 setup";
-      documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      reloadIfChanged = true;
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        ExecStartPre = "${pkgs.kmod}/bin/modprobe jool";
-        ExecStart    = "${jool-cli}/bin/jool file handle ${nat64Conf}";
-        ExecStop     = "${jool-cli}/bin/jool -f ${nat64Conf} instance remove";
-      } // hardening;
-    };
-
-    systemd.services.jool-siit = lib.mkIf cfg.siit.enable {
-      description = "Jool, SIIT setup";
-      documentation = [ "https://nicmx.github.io/Jool/en/documentation.html" ];
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      reloadIfChanged = true;
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
-        ExecStartPre = "${pkgs.kmod}/bin/modprobe jool_siit";
-        ExecStart    = "${jool-cli}/bin/jool_siit file handle ${siitConf}";
-        ExecStop     = "${jool-cli}/bin/jool_siit -f ${siitConf} instance remove";
-      } // hardening;
-    };
-
-    system.checks = lib.singleton (pkgs.runCommand "jool-validated" {
-      nativeBuildInputs = [ pkgs.buildPackages.jool-cli ];
-      preferLocalBuild = true;
-    } ''
-      printf 'Validating Jool configuration... '
-      ${lib.optionalString cfg.siit.enable "jool_siit file check ${siitConf}"}
-      ${lib.optionalString cfg.nat64.enable "jool file check ${nat64Conf}"}
-      printf 'ok\n'
-      touch "$out"
-    '');
-
-    networking.jool.nat64.config = mkDefaultAttrs defaultNat64;
-    networking.jool.siit.config  = mkDefaultAttrs defaultSiit;
+    # Install services for each instance
+    systemd.services = lib.mkMerge
+      (lib.mapAttrsToList makeNat64Unit cfg.nat64 ++
+       lib.mapAttrsToList makeSiitUnit cfg.siit);
 
+    # Check the configuration of each instance
+    system.checks = lib.optional (cfg.nat64 != {} || cfg.siit != {})
+      (pkgs.runCommand "jool-validated"
+        {
+          nativeBuildInputs = with pkgs.buildPackages; [ jool-cli ];
+          preferLocalBuild = true;
+        }
+        (lib.concatStrings
+          (lib.mapAttrsToList checkNat64 cfg.nat64 ++
+           lib.mapAttrsToList checkSiit cfg.siit)));
   };
 
   meta.maintainers = with lib.maintainers; [ rnhmjoj ];
diff --git a/nixos/modules/services/networking/kea.nix b/nixos/modules/services/networking/kea.nix
index 945f4113bd47d..656ddd41fd12b 100644
--- a/nixos/modules/services/networking/kea.nix
+++ b/nixos/modules/services/networking/kea.nix
@@ -255,6 +255,7 @@ in
       User = "kea";
       ConfigurationDirectory = "kea";
       RuntimeDirectory = "kea";
+      RuntimeDirectoryPreserve = true;
       StateDirectory = "kea";
       UMask = "0077";
     };
@@ -324,6 +325,9 @@ in
         "network-online.target"
         "time-sync.target"
       ];
+      wants = [
+        "network-online.target"
+      ];
       wantedBy = [
         "multi-user.target"
       ];
@@ -371,6 +375,9 @@ in
         "network-online.target"
         "time-sync.target"
       ];
+      wants = [
+        "network-online.target"
+      ];
       wantedBy = [
         "multi-user.target"
       ];
@@ -412,6 +419,7 @@ in
         "https://kea.readthedocs.io/en/kea-${package.version}/arm/ddns.html"
       ];
 
+      wants = [ "network-online.target" ];
       after = [
         "network-online.target"
         "time-sync.target"
diff --git a/nixos/modules/services/networking/keepalived/default.nix b/nixos/modules/services/networking/keepalived/default.nix
index 29fbea5545c36..599dfd52e271f 100644
--- a/nixos/modules/services/networking/keepalived/default.nix
+++ b/nixos/modules/services/networking/keepalived/default.nix
@@ -59,9 +59,11 @@ let
         ${optionalString i.vmacXmitBase "vmac_xmit_base"}
 
         ${optionalString (i.unicastSrcIp != null) "unicast_src_ip ${i.unicastSrcIp}"}
-        unicast_peer {
-          ${concatStringsSep "\n" i.unicastPeers}
-        }
+        ${optionalString (builtins.length i.unicastPeers > 0) ''
+          unicast_peer {
+            ${concatStringsSep "\n" i.unicastPeers}
+          }
+        ''}
 
         virtual_ipaddress {
           ${concatMapStringsSep "\n" virtualIpLine i.virtualIps}
@@ -138,6 +140,7 @@ let
 
 in
 {
+  meta.maintainers = [ lib.maintainers.raitobezarius ];
 
   options = {
     services.keepalived = {
@@ -150,6 +153,14 @@ in
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to automatically allow VRRP and AH packets in the firewall.
+        '';
+      };
+
       enableScriptSecurity = mkOption {
         type = types.bool;
         default = false;
@@ -282,6 +293,19 @@ in
 
     assertions = flatten (map vrrpInstanceAssertions vrrpInstances);
 
+    networking.firewall = lib.mkIf cfg.openFirewall {
+      extraCommands = ''
+        # Allow VRRP and AH packets
+        ip46tables -A nixos-fw -p vrrp -m comment --comment "services.keepalived.openFirewall" -j ACCEPT
+        ip46tables -A nixos-fw -p ah -m comment --comment "services.keepalived.openFirewall" -j ACCEPT
+      '';
+
+      extraStopCommands = ''
+        ip46tables -D nixos-fw -p vrrp -m comment --comment "services.keepalived.openFirewall" -j ACCEPT
+        ip46tables -D nixos-fw -p ah -m comment --comment "services.keepalived.openFirewall" -j ACCEPT
+      '';
+    };
+
     systemd.timers.keepalived-boot-delay = {
       description = "Keepalive Daemon delay to avoid instant transition to MASTER state";
       after = [ "network.target" "network-online.target" "syslog.target" ];
diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix
index e97195d829194..d4bd81629c979 100644
--- a/nixos/modules/services/networking/knot.nix
+++ b/nixos/modules/services/networking/knot.nix
@@ -5,10 +5,114 @@ with lib;
 let
   cfg = config.services.knot;
 
-  configFile = pkgs.writeTextFile {
+  yamlConfig = let
+    result = assert secsCheck; nix2yaml cfg.settings;
+
+    secAllow = n: hasPrefix "mod-" n || elem n [
+      "module"
+      "server" "xdp" "control"
+      "log"
+      "statistics" "database"
+      "keystore" "key" "remote" "remotes" "acl" "submission" "policy"
+      "template"
+      "zone"
+      "include"
+    ];
+    secsCheck = let
+      secsBad = filter (n: !secAllow n) (attrNames cfg.settings);
+    in if secsBad == [] then true else throw
+      ("services.knot.settings contains unknown sections: " + toString secsBad);
+
+    nix2yaml = nix_def: concatStrings (
+        # We output the config section in the upstream-mandated order.
+        # Ordering is important due to forward-references not being allowed.
+        # See definition of conf_export and 'const yp_item_t conf_schema'
+        # upstream for reference.  Last updated for 3.3.
+        # When changing the set of sections, also update secAllow above.
+        [ (sec_list_fa "id" nix_def "module") ]
+        ++ map (sec_plain nix_def)
+          [ "server" "xdp" "control" ]
+        ++ [ (sec_list_fa "target" nix_def "log") ]
+        ++ map (sec_plain nix_def)
+          [  "statistics" "database" ]
+        ++ map (sec_list_fa "id" nix_def)
+          [ "keystore" "key" "remote" "remotes" "acl" "submission" "policy" ]
+
+        # Export module sections before the template section.
+        ++ map (sec_list_fa "id" nix_def) (filter (hasPrefix "mod-") (attrNames nix_def))
+
+        ++ [ (sec_list_fa "id" nix_def "template") ]
+        ++ [ (sec_list_fa "domain" nix_def "zone") ]
+        ++ [ (sec_plain nix_def "include") ]
+      );
+
+    # A plain section contains directly attributes (we don't really check that ATM).
+    sec_plain = nix_def: sec_name: if !hasAttr sec_name nix_def then "" else
+      n2y "" { ${sec_name} = nix_def.${sec_name}; };
+
+    # This section contains a list of attribute sets.  In each of the sets
+    # there's an attribute (`fa_name`, typically "id") that must exist and come first.
+    # Alternatively we support using attribute sets instead of lists; example diff:
+    # -template = [ { id = "default"; /* other attributes */ }   { id = "foo"; } ]
+    # +template = { default = {       /* those attributes */ };  foo = { };      }
+    sec_list_fa = fa_name: nix_def: sec_name: if !hasAttr sec_name nix_def then "" else
+      let
+        elem2yaml = fa_val: other_attrs:
+          "  - " + n2y "" { ${fa_name} = fa_val; }
+          + "    " + n2y "    " other_attrs
+          + "\n";
+        sec = nix_def.${sec_name};
+      in
+        sec_name + ":\n" +
+          (if isList sec
+            then flip concatMapStrings sec
+              (elem: elem2yaml elem.${fa_name} (removeAttrs elem [ fa_name ]))
+            else concatStrings (mapAttrsToList elem2yaml sec)
+          );
+
+    # This convertor doesn't care about ordering of attributes.
+    # TODO: it could probably be simplified even more, now that it's not
+    # to be used directly, but we might want some other tweaks, too.
+    n2y = indent: val:
+      if doRecurse val then concatStringsSep "\n${indent}"
+        (mapAttrsToList
+          # This is a bit wacky - set directly under a set would start on bad indent,
+          # so we start those on a new line, but not other types of attribute values.
+          (aname: aval: "${aname}:${if doRecurse aval then "\n${indent}  " else " "}"
+            + n2y (indent + "  ") aval)
+          val
+        )
+        + "\n"
+        else
+      /*
+      if isList val && stringLength indent < 4 then concatMapStrings
+        (elem: "\n${indent}- " + n2y (indent + "  ") elem)
+        val
+        else
+      */
+      if isList val /* and long indent */ then
+        "[ " + concatMapStringsSep ", " quoteString val + " ]" else
+      if isBool val then (if val then "on" else "off") else
+      quoteString val;
+
+    # We don't want paths like ./my-zone.txt be converted to plain strings.
+    quoteString = s: ''"${if builtins.typeOf s == "path" then s else toString s}"'';
+    # We don't want to walk the insides of derivation attributes.
+    doRecurse = val: isAttrs val && !isDerivation val;
+
+  in result;
+
+  configFile = if cfg.settingsFile != null then
+    # Note: with extraConfig, the 23.05 compat code did include keyFiles from settingsFile.
+    assert cfg.settings == {} && (cfg.keyFiles == [] || cfg.extraConfig != null);
+    cfg.settingsFile
+  else
+    mkConfigFile yamlConfig;
+
+  mkConfigFile = configString: pkgs.writeTextFile {
     name = "knot.conf";
-    text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" +
-           cfg.extraConfig;
+    text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" + configString;
+    # TODO: maybe we could do some checks even when private keys complicate this?
     checkPhase = lib.optionalString (cfg.keyFiles == []) ''
       ${cfg.package}/bin/knotc --config=$out conf-check
     '';
@@ -60,24 +164,33 @@ in {
         '';
       };
 
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
+      settings = mkOption {
+        type = types.attrs;
+        default = {};
         description = lib.mdDoc ''
-          Extra lines to be added verbatim to knot.conf
+          Extra configuration as nix values.
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.knot-dns;
-        defaultText = literalExpression "pkgs.knot-dns";
+      settingsFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
         description = lib.mdDoc ''
-          Which Knot DNS package to use
+          As alternative to ``settings``, you can provide whole configuration
+          directly in the almost-YAML format of Knot DNS.
+          You might want to utilize ``pkgs.writeText "knot.conf" "longConfigString"`` for this.
         '';
       };
+
+      package = mkPackageOption pkgs "knot-dns" { };
     };
   };
+  imports = [
+    # Compatibility with NixOS 23.05.
+    (mkChangedOptionModule [ "services" "knot" "extraConfig" ] [ "services" "knot" "settingsFile" ]
+      (config: mkConfigFile config.services.knot.extraConfig)
+    )
+  ];
 
   config = mkIf config.services.knot.enable {
     users.groups.knot = {};
@@ -87,6 +200,8 @@ in {
       description = "Knot daemon user";
     };
 
+    environment.etc."knot/knot.conf".source = configFile; # just for user's convenience
+
     systemd.services.knot = {
       unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/";
       description = cfg.package.meta.description;
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index 3ad757133a606..0c7363e564dc0 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -57,14 +57,8 @@ in {
         and give commands interactively to kresd@1.service.
       '';
     };
-    package = mkOption {
-      type = types.package;
-      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; }";
+    package = mkPackageOption pkgs "knot-resolver" {
+      example = "knot-resolver.override { extraFeatures = true; }";
     };
     extraConfig = mkOption {
       type = types.lines;
diff --git a/nixos/modules/services/networking/lambdabot.nix b/nixos/modules/services/networking/lambdabot.nix
index 8609bc9719622..01914097ad726 100644
--- a/nixos/modules/services/networking/lambdabot.nix
+++ b/nixos/modules/services/networking/lambdabot.nix
@@ -24,12 +24,7 @@ in
         description = lib.mdDoc "Enable the Lambdabot IRC bot";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.lambdabot;
-        defaultText = literalExpression "pkgs.lambdabot";
-        description = lib.mdDoc "Used lambdabot package";
-      };
+      package = mkPackageOption pkgs "lambdabot" { };
 
       script = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/networking/legit.nix b/nixos/modules/services/networking/legit.nix
index 90234f3955e8c..ff8e0dd4f93ca 100644
--- a/nixos/modules/services/networking/legit.nix
+++ b/nixos/modules/services/networking/legit.nix
@@ -7,7 +7,7 @@ let
     mdDoc
     mkIf
     mkOption
-    mkPackageOptionMD
+    mkPackageOption
     optionalAttrs
     optional
     types;
@@ -25,7 +25,7 @@ in
   options.services.legit = {
     enable = mkEnableOption (mdDoc "legit git web frontend");
 
-    package = mkPackageOptionMD pkgs "legit-web" { };
+    package = mkPackageOption pkgs "legit-web" { };
 
     user = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/networking/lokinet.nix b/nixos/modules/services/networking/lokinet.nix
index f6bc314ed260d..8f64d3f0119f9 100644
--- a/nixos/modules/services/networking/lokinet.nix
+++ b/nixos/modules/services/networking/lokinet.nix
@@ -9,12 +9,7 @@ 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.";
-    };
+    package = mkPackageOption pkgs "lokinet" { };
 
     useLocally = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/networking/miniupnpd.nix b/nixos/modules/services/networking/miniupnpd.nix
index 64aacaf350404..116298dc6b1db 100644
--- a/nixos/modules/services/networking/miniupnpd.nix
+++ b/nixos/modules/services/networking/miniupnpd.nix
@@ -13,8 +13,17 @@ let
       listening_ip=${range}
     '') cfg.internalIPs}
 
+    ${lib.optionalString (firewall == "nftables") ''
+      upnp_table_name=miniupnpd
+      upnp_nat_table_name=miniupnpd
+    ''}
+
     ${cfg.appendConfig}
   '';
+  firewall = if config.networking.nftables.enable then "nftables" else "iptables";
+  miniupnpd = pkgs.miniupnpd.override { inherit firewall; };
+  firewallScripts = lib.optionals (firewall == "iptables")
+    ([ "iptables"] ++ lib.optional (config.networking.enableIPv6) "ip6tables");
 in
 {
   options = {
@@ -57,20 +66,50 @@ in
   };
 
   config = mkIf cfg.enable {
-    networking.firewall.extraCommands = ''
-      ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_init.sh -i ${cfg.externalInterface}
-    '';
+    networking.firewall.extraCommands = lib.mkIf (firewallScripts != []) (builtins.concatStringsSep "\n" (map (fw: ''
+      EXTIF=${cfg.externalInterface} ${pkgs.bash}/bin/bash -x ${miniupnpd}/etc/miniupnpd/${fw}_init.sh
+    '') firewallScripts));
+
+    networking.firewall.extraStopCommands = lib.mkIf (firewallScripts != []) (builtins.concatStringsSep "\n" (map (fw: ''
+      EXTIF=${cfg.externalInterface} ${pkgs.bash}/bin/bash -x ${miniupnpd}/etc/miniupnpd/${fw}_removeall.sh
+    '') firewallScripts));
 
-    networking.firewall.extraStopCommands = ''
-      ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_removeall.sh -i ${cfg.externalInterface}
-    '';
+    networking.nftables = lib.mkIf (firewall == "nftables") {
+      # see nft_init in ${miniupnpd-nftables}/etc/miniupnpd
+      tables.miniupnpd = {
+        family = "inet";
+        # The following is omitted because it's expected that the firewall is to be responsible for it.
+        #
+        # chain forward {
+        #   type filter hook forward priority filter; policy drop;
+        #   jump miniupnpd
+        # }
+        #
+        # Otherwise, it quickly gets ugly with (potentially) two forward chains with "policy drop".
+        # This means the chain "miniupnpd" never actually gets triggered and is simply there to satisfy
+        # miniupnpd. If you're doing it yourself (without networking.firewall), the easiest way to get
+        # it to work is adding a rule "ct status dnat accept" - this is what networking.firewall does.
+        # If you don't want to simply accept forwarding for all "ct status dnat" packets, override
+        # upnp_table_name with whatever your table is, create a chain "miniupnpd" in your table and
+        # jump into it from your forward chain.
+        content = ''
+          chain miniupnpd {}
+          chain prerouting_miniupnpd {
+            type nat hook prerouting priority dstnat; policy accept;
+          }
+          chain postrouting_miniupnpd {
+            type nat hook postrouting priority srcnat; policy accept;
+          }
+        '';
+      };
+    };
 
     systemd.services.miniupnpd = {
       description = "MiniUPnP daemon";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.miniupnpd}/bin/miniupnpd -f ${configFile}";
+        ExecStart = "${miniupnpd}/bin/miniupnpd -f ${configFile}";
         PIDFile = "/run/miniupnpd.pid";
         Type = "forking";
       };
diff --git a/nixos/modules/services/networking/miredo.nix b/nixos/modules/services/networking/miredo.nix
index d15a55b4d7d65..0c43839c15ab4 100644
--- a/nixos/modules/services/networking/miredo.nix
+++ b/nixos/modules/services/networking/miredo.nix
@@ -22,14 +22,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "the Miredo IPv6 tunneling service");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.miredo;
-        defaultText = literalExpression "pkgs.miredo";
-        description = lib.mdDoc ''
-          The package to use for the miredo daemon's binary.
-        '';
-      };
+      package = mkPackageOption pkgs "miredo" { };
 
       serverAddress = mkOption {
         default = "teredo.remlab.net";
diff --git a/nixos/modules/services/networking/morty.nix b/nixos/modules/services/networking/morty.nix
index 72514764a7c6e..6954596addfd2 100644
--- a/nixos/modules/services/networking/morty.nix
+++ b/nixos/modules/services/networking/morty.nix
@@ -42,12 +42,7 @@ in
         description = lib.mdDoc "Request timeout in seconds.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.morty;
-        defaultText = literalExpression "pkgs.morty";
-        description = lib.mdDoc "morty package to use.";
-      };
+      package = mkPackageOption pkgs "morty" { };
 
       port = mkOption {
         type = types.port;
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index c53d86c0babcd..ad9eefb422525 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -482,14 +482,7 @@ let
   globalOptions = with types; {
     enable = mkEnableOption (lib.mdDoc "the MQTT Mosquitto broker");
 
-    package = mkOption {
-      type = package;
-      default = pkgs.mosquitto;
-      defaultText = literalExpression "pkgs.mosquitto";
-      description = lib.mdDoc ''
-        Mosquitto package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "mosquitto" { };
 
     bridges = mkOption {
       type = attrsOf bridgeOptions;
@@ -603,6 +596,7 @@ in
     systemd.services.mosquitto = {
       description = "Mosquitto MQTT Broker Daemon";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       serviceConfig = {
         Type = "notify";
diff --git a/nixos/modules/services/networking/mtr-exporter.nix b/nixos/modules/services/networking/mtr-exporter.nix
index 43ebbbe96d05d..38bc0401a7e65 100644
--- a/nixos/modules/services/networking/mtr-exporter.nix
+++ b/nixos/modules/services/networking/mtr-exporter.nix
@@ -2,63 +2,104 @@
 
 let
   inherit (lib)
-    maintainers types mkEnableOption mkOption mkIf
-    literalExpression escapeShellArg escapeShellArgs;
+    maintainers types literalExpression
+    escapeShellArg escapeShellArgs
+    mkEnableOption mkOption mkRemovedOptionModule mkIf mdDoc
+    mkPackageOption optionalString concatMapStrings concatStringsSep;
+
   cfg = config.services.mtr-exporter;
+
+  jobsConfig = pkgs.writeText "mtr-exporter.conf" (concatMapStrings (job: ''
+    ${job.name} -- ${job.schedule} -- ${concatStringsSep " " job.flags} ${job.address}
+  '') cfg.jobs);
 in {
+  imports = [
+    (mkRemovedOptionModule [ "services" "mtr-exporter" "target" ] "Use services.mtr-exporter.jobs instead.")
+    (mkRemovedOptionModule [ "services" "mtr-exporter" "mtrFlags" ] "Use services.mtr-exporter.jobs.<job>.flags instead.")
+  ];
+
   options = {
     services = {
       mtr-exporter = {
-        enable = mkEnableOption (lib.mdDoc "a Prometheus exporter for MTR");
+        enable = mkEnableOption (mdDoc "a Prometheus exporter for MTR");
 
-        target = mkOption {
+        address = mkOption {
           type = types.str;
-          example = "example.org";
-          description = lib.mdDoc "Target to check using MTR.";
-        };
-
-        interval = mkOption {
-          type = types.int;
-          default = 60;
-          description = lib.mdDoc "Interval between MTR checks in seconds.";
+          default = "127.0.0.1";
+          description = lib.mdDoc "Listen address for MTR exporter.";
         };
 
         port = mkOption {
           type = types.port;
           default = 8080;
-          description = lib.mdDoc "Listen port for MTR exporter.";
+          description = mdDoc "Listen port for MTR exporter.";
         };
 
-        address = mkOption {
-          type = types.str;
-          default = "127.0.0.1";
-          description = lib.mdDoc "Listen address for MTR exporter.";
+        extraFlags = mkOption {
+          type = types.listOf types.str;
+          default = [];
+          example = ["-flag.deprecatedMetrics"];
+          description = mdDoc ''
+            Extra command line options to pass to MTR exporter.
+          '';
         };
 
-        mtrFlags = mkOption {
-          type = with types; listOf str;
-          default = [];
-          example = ["-G1"];
-          description = lib.mdDoc "Additional flags to pass to MTR.";
+        package = mkPackageOption pkgs "mtr-exporter" { };
+
+        mtrPackage = mkPackageOption pkgs "mtr" { };
+
+        jobs = mkOption {
+          description = mdDoc "List of MTR jobs. Will be added to /etc/mtr-exporter.conf";
+          type = types.nonEmptyListOf (types.submodule {
+            options = {
+              name = mkOption {
+                type = types.str;
+                description = mdDoc "Name of ICMP pinging job.";
+              };
+
+              address = mkOption {
+                type = types.str;
+                example = "host.example.org:1234";
+                description = mdDoc "Target address for MTR client.";
+              };
+
+              schedule = mkOption {
+                type = types.str;
+                default = "@every 60s";
+                example = "@hourly";
+                description = mdDoc "Schedule of MTR checks. Also accepts Cron format.";
+              };
+
+              flags = mkOption {
+                type = with types; listOf str;
+                default = [];
+                example = ["-G1"];
+                description = mdDoc "Additional flags to pass to MTR.";
+              };
+            };
+          });
         };
       };
     };
   };
 
   config = mkIf cfg.enable {
+    environment.etc."mtr-exporter.conf" = {
+      source = jobsConfig;
+    };
+
     systemd.services.mtr-exporter = {
-      script = ''
-        exec ${pkgs.mtr-exporter}/bin/mtr-exporter \
-          -mtr ${pkgs.mtr}/bin/mtr \
-          -schedule '@every ${toString cfg.interval}s' \
-          -bind ${escapeShellArg cfg.address}:${toString cfg.port} \
-          -- \
-          ${escapeShellArgs (cfg.mtrFlags ++ [ cfg.target ])}
-      '';
       wantedBy = [ "multi-user.target" ];
       requires = [ "network.target" ];
       after = [ "network.target" ];
       serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/mtr-exporter \
+            -mtr '${cfg.mtrPackage}/bin/mtr' \
+            -bind ${escapeShellArg "${cfg.address}:${toString cfg.port}"} \
+            -jobs '${jobsConfig}' \
+            ${escapeShellArgs cfg.extraFlags}
+        '';
         Restart = "on-failure";
         # Hardening
         CapabilityBoundingSet = [ "" ];
diff --git a/nixos/modules/services/networking/mullvad-vpn.nix b/nixos/modules/services/networking/mullvad-vpn.nix
index 82e68bf92af1f..5da4ca1d1d803 100644
--- a/nixos/modules/services/networking/mullvad-vpn.nix
+++ b/nixos/modules/services/networking/mullvad-vpn.nix
@@ -23,12 +23,10 @@ with lib;
       '';
     };
 
-    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.
+    package = mkPackageOption pkgs "mullvad" {
+      example = "mullvad-vpn";
+      extraDescription = ''
+        `pkgs.mullvad` only provides the CLI tool, `pkgs.mullvad-vpn` provides both the CLI and the GUI.
       '';
     };
   };
@@ -55,7 +53,7 @@ with lib;
     systemd.services.mullvad-daemon = {
       description = "Mullvad VPN daemon";
       wantedBy = [ "multi-user.target" ];
-      wants = [ "network.target" ];
+      wants = [ "network.target" "network-online.target" ];
       after = [
         "network-online.target"
         "NetworkManager.service"
@@ -65,7 +63,9 @@ with lib;
         pkgs.iproute2
         # Needed for ping
         "/run/wrappers"
-      ];
+        # See https://github.com/NixOS/nixpkgs/issues/262681
+      ] ++ (lib.optional config.networking.resolvconf.enable
+        config.networking.resolvconf.package);
       startLimitBurst = 5;
       startLimitIntervalSec = 20;
       serviceConfig = {
@@ -76,5 +76,5 @@ with lib;
     };
   };
 
-  meta.maintainers = with maintainers; [ patricksjackson ymarkus ];
+  meta.maintainers = with maintainers; [ arcuru ymarkus ];
 }
diff --git a/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix
index bd403e109c2af..42ffc3c88426c 100644
--- a/nixos/modules/services/networking/multipath.nix
+++ b/nixos/modules/services/networking/multipath.nix
@@ -24,12 +24,7 @@ in {
 
     enable = mkEnableOption (lib.mdDoc "the device mapper multipath (DM-MP) daemon");
 
-    package = mkOption {
-      type = package;
-      description = lib.mdDoc "multipath-tools package to use";
-      default = pkgs.multipath-tools;
-      defaultText = lib.literalExpression "pkgs.multipath-tools";
-    };
+    package = mkPackageOption pkgs "multipath-tools" { };
 
     devices = mkOption {
       default = [ ];
@@ -546,8 +541,9 @@ in {
     # We do not have systemd in stage-1 boot so must invoke `multipathd`
     # with the `-1` argument which disables systemd calls. Invoke `multipath`
     # to display the multipath mappings in the output of `journalctl -b`.
+    # TODO: Implement for systemd stage 1
     boot.initrd.kernelModules = [ "dm-multipath" "dm-service-time" ];
-    boot.initrd.postDeviceCommands = ''
+    boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       modprobe -a dm-multipath dm-service-time
       multipathd -s
       (set -x && sleep 1 && multipath -ll)
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index 20c2eff11e625..0cd80e134ace4 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -119,12 +119,7 @@ in
         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 = lib.mdDoc "Overridable attribute of the murmur package to use.";
-      };
+      package = mkPackageOption pkgs "murmur" { };
 
       password = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix
index 528a51c1f3af4..47d2b16a15018 100644
--- a/nixos/modules/services/networking/mxisd.nix
+++ b/nixos/modules/services/networking/mxisd.nix
@@ -39,12 +39,7 @@ in {
     services.mxisd = {
       enable = mkEnableOption (lib.mdDoc "matrix federated identity server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.ma1sd;
-        defaultText = literalExpression "pkgs.ma1sd";
-        description = lib.mdDoc "The mxisd/ma1sd package to use";
-      };
+      package = mkPackageOption pkgs "ma1sd" { };
 
       environmentFile = mkOption {
         type = types.nullOr types.str;
diff --git a/nixos/modules/services/networking/nar-serve.nix b/nixos/modules/services/networking/nar-serve.nix
index beee53c8a2425..02b8979bd8bc7 100644
--- a/nixos/modules/services/networking/nar-serve.nix
+++ b/nixos/modules/services/networking/nar-serve.nix
@@ -6,11 +6,11 @@ let
 in
 {
   meta = {
-    maintainers = [ maintainers.rizary ];
+    maintainers = [ maintainers.rizary maintainers.zimbatm ];
   };
   options = {
     services.nar-serve = {
-      enable = mkEnableOption (lib.mdDoc "Serve NAR file contents via HTTP");
+      enable = mkEnableOption (lib.mdDoc "serving NAR file contents via HTTP");
 
       port = mkOption {
         type = types.port;
diff --git a/nixos/modules/services/networking/nat-nftables.nix b/nixos/modules/services/networking/nat-nftables.nix
index 4b2317ca2ffc1..7aa93d8a64b16 100644
--- a/nixos/modules/services/networking/nat-nftables.nix
+++ b/nixos/modules/services/networking/nat-nftables.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, ... }:
 
 with lib;
 
@@ -35,26 +35,18 @@ let
 
   mkTable = { ipVer, dest, ipSet, forwardPorts, dmzHost }:
     let
-      # nftables does not support both port and port range as values in a dnat map.
-      # e.g. "dnat th dport map { 80 : 10.0.0.1 . 80, 443 : 10.0.0.2 . 900-1000 }"
-      # So we split them.
-      fwdPorts = filter (x: length (splitString "-" x.destination) == 1) forwardPorts;
-      fwdPortsRange = filter (x: length (splitString "-" x.destination) > 1) forwardPorts;
-
       # nftables maps for port forward
       # l4proto . dport : addr . port
-      toFwdMap = forwardPorts: toNftSet (map
+      fwdMap = toNftSet (map
         (fwd:
           with (splitIPPorts fwd.destination);
           "${fwd.proto} . ${toNftRange fwd.sourcePort} : ${IP} . ${ports}"
         )
         forwardPorts);
-      fwdMap = toFwdMap fwdPorts;
-      fwdRangeMap = toFwdMap fwdPortsRange;
 
       # nftables maps for port forward loopback dnat
       # daddr . l4proto . dport : addr . port
-      toFwdLoopDnatMap = forwardPorts: toNftSet (concatMap
+      fwdLoopDnatMap = toNftSet (concatMap
         (fwd: map
           (loopbackip:
             with (splitIPPorts fwd.destination);
@@ -62,8 +54,6 @@ let
           )
           fwd.loopbackIPs)
         forwardPorts);
-      fwdLoopDnatMap = toFwdLoopDnatMap fwdPorts;
-      fwdLoopDnatRangeMap = toFwdLoopDnatMap fwdPortsRange;
 
       # nftables set for port forward loopback snat
       # daddr . l4proto . dport
@@ -79,17 +69,11 @@ let
         type nat hook prerouting priority dstnat;
 
         ${optionalString (fwdMap != "") ''
-          iifname "${cfg.externalInterface}" dnat meta l4proto . th dport map { ${fwdMap} } comment "port forward"
-        ''}
-        ${optionalString (fwdRangeMap != "") ''
-          iifname "${cfg.externalInterface}" dnat meta l4proto . th dport map { ${fwdRangeMap} } comment "port forward"
+          iifname "${cfg.externalInterface}" meta l4proto { tcp, udp } dnat meta l4proto . th dport map { ${fwdMap} } comment "port forward"
         ''}
 
         ${optionalString (fwdLoopDnatMap != "") ''
-          dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from other hosts behind NAT"
-        ''}
-        ${optionalString (fwdLoopDnatRangeMap != "") ''
-          dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatRangeMap} } comment "port forward loopback from other hosts behind NAT"
+          meta l4proto { tcp, udp } dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from other hosts behind NAT"
         ''}
 
         ${optionalString (dmzHost != null) ''
@@ -116,10 +100,7 @@ let
         type nat hook output priority mangle;
 
         ${optionalString (fwdLoopDnatMap != "") ''
-          dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from the host itself"
-        ''}
-        ${optionalString (fwdLoopDnatRangeMap != "") ''
-          dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatRangeMap} } comment "port forward loopback from the host itself"
+          meta l4proto { tcp, udp } dnat ${ipVer} daddr . meta l4proto . th dport map { ${fwdLoopDnatMap} } comment "port forward loopback from the host itself"
         ''}
       }
     '';
diff --git a/nixos/modules/services/networking/nbd.nix b/nixos/modules/services/networking/nbd.nix
index 454380aa3154c..b4bf7ede84632 100644
--- a/nixos/modules/services/networking/nbd.nix
+++ b/nixos/modules/services/networking/nbd.nix
@@ -117,6 +117,7 @@ in
     boot.kernelModules = [ "nbd" ];
 
     systemd.services.nbd-server = {
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       before = [ "multi-user.target" ];
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix
index e1a8c6740f57e..e13876172dac6 100644
--- a/nixos/modules/services/networking/nebula.nix
+++ b/nixos/modules/services/networking/nebula.nix
@@ -27,12 +27,7 @@ in
               description = lib.mdDoc "Enable or disable this network.";
             };
 
-            package = mkOption {
-              type = types.package;
-              default = pkgs.nebula;
-              defaultText = literalExpression "pkgs.nebula";
-              description = lib.mdDoc "Nebula derivation to use.";
-            };
+            package = mkPackageOption pkgs "nebula" { };
 
             ca = mkOption {
               type = types.path;
@@ -201,7 +196,7 @@ in
             before = [ "sshd.service" ];
             wantedBy = [ "multi-user.target" ];
             serviceConfig = {
-              Type = "simple";
+              Type = "notify";
               Restart = "always";
               ExecStart = "${netCfg.package}/bin/nebula -config ${configFile}";
               UMask = "0027";
diff --git a/nixos/modules/services/networking/netbird.nix b/nixos/modules/services/networking/netbird.nix
index 647c0ce3e6d1f..4b0bd63e9dbc9 100644
--- a/nixos/modules/services/networking/netbird.nix
+++ b/nixos/modules/services/networking/netbird.nix
@@ -11,12 +11,7 @@ in {
 
   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";
-    };
+    package = mkPackageOption pkgs "netbird" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/networking/netclient.nix b/nixos/modules/services/networking/netclient.nix
new file mode 100644
index 0000000000000..43b8f07cca046
--- /dev/null
+++ b/nixos/modules/services/networking/netclient.nix
@@ -0,0 +1,27 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.netclient;
+in
+{
+  meta.maintainers = with lib.maintainers; [ wexder ];
+
+  options.services.netclient = {
+    enable = lib.mkEnableOption (lib.mdDoc "Netclient Daemon");
+    package = lib.mkPackageOption pkgs "netclient" { };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    systemd.services.netclient = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      description = "Netclient Daemon";
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${lib.getExe cfg.package} daemon";
+        Restart = "on-failure";
+        RestartSec = "15s";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index e28f96f7a6d6c..c96439cf2641a 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -4,8 +4,9 @@ with lib;
 
 let
   cfg = config.networking.networkmanager;
+  ini = pkgs.formats.ini { };
 
-  delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [];
+  delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [ ];
 
   enableIwd = cfg.wifi.backend == "iwd";
 
@@ -30,17 +31,15 @@ let
   configFile = pkgs.writeText "NetworkManager.conf" (lib.concatStringsSep "\n" [
     (mkSection "main" {
       plugins = "keyfile";
-      dhcp = cfg.dhcp;
-      dns = cfg.dns;
+      inherit (cfg) dhcp dns;
       # If resolvconf is disabled that means that resolv.conf is managed by some other module.
       rc-manager =
         if config.networking.resolvconf.enable then "resolvconf"
         else "unmanaged";
-      firewall-backend = cfg.firewallBackend;
     })
     (mkSection "keyfile" {
       unmanaged-devices =
-        if cfg.unmanaged == [] then null
+        if cfg.unmanaged == [ ] then null
         else lib.concatStringsSep ";" cfg.unmanaged;
     })
     (mkSection "logging" {
@@ -103,7 +102,7 @@ let
   };
 
   macAddressOpt = mkOption {
-    type = types.either types.str (types.enum ["permanent" "preserve" "random" "stable"]);
+    type = types.either types.str (types.enum [ "permanent" "preserve" "random" "stable" ]);
     default = "preserve";
     example = "00:11:22:33:44:55";
     description = lib.mdDoc ''
@@ -126,7 +125,8 @@ let
     pkgs.wpa_supplicant
   ];
 
-in {
+in
+{
 
   meta = {
     maintainers = teams.freedesktop.members;
@@ -156,7 +156,7 @@ in {
           int
           str
         ]));
-        default = {};
+        default = { };
         description = lib.mdDoc ''
           Configuration for the [connection] section of NetworkManager.conf.
           Refer to
@@ -186,7 +186,7 @@ in {
 
       unmanaged = mkOption {
         type = types.listOf types.str;
-        default = [];
+        default = [ ];
         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,
@@ -232,15 +232,6 @@ in {
         '';
       };
 
-      firewallBackend = mkOption {
-        type = types.enum [ "iptables" "nftables" "none" ];
-        default = "iptables";
-        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.
-        '';
-      };
-
       logLevel = mkOption {
         type = types.enum [ "OFF" "ERR" "WARN" "INFO" "DEBUG" "TRACE" ];
         default = "WARN";
@@ -251,7 +242,7 @@ in {
 
       appendNameservers = mkOption {
         type = types.listOf types.str;
-        default = [];
+        default = [ ];
         description = lib.mdDoc ''
           A list of name servers that should be appended
           to the ones configured in NetworkManager or received by DHCP.
@@ -260,7 +251,7 @@ in {
 
       insertNameservers = mkOption {
         type = types.listOf types.str;
-        default = [];
+        default = [ ];
         description = lib.mdDoc ''
           A list of name servers that should be inserted before
           the ones configured in NetworkManager or received by DHCP.
@@ -336,23 +327,23 @@ in {
             };
           };
         });
-        default = [];
+        default = [ ];
         example = literalExpression ''
-        [ {
-              source = pkgs.writeText "upHook" '''
-
-                if [ "$2" != "up" ]; then
-                    logger "exit: event $2 != up"
-                    exit
-                fi
-
-                # coreutils and iproute are in PATH too
-                logger "Device $DEVICE_IFACE coming up"
+          [ {
+            source = pkgs.writeText "upHook" '''
+              if [ "$2" != "up" ]; then
+                logger "exit: event $2 != up"
+                exit
+              fi
+
+              # coreutils and iproute are in PATH too
+              logger "Device $DEVICE_IFACE coming up"
             ''';
             type = "basic";
-        } ]'';
+          } ]
+        '';
         description = lib.mdDoc ''
-          A list of scripts which will be executed in response to  network  events.
+          A list of scripts which will be executed in response to network events.
         '';
       };
 
@@ -369,16 +360,94 @@ in {
         '';
       };
 
-      enableFccUnlock = mkOption {
-        type = types.bool;
-        default = false;
+      fccUnlockScripts = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            id = mkOption {
+              type = types.str;
+              description = lib.mdDoc "vid:pid of either the PCI or USB vendor and product ID";
+            };
+            path = mkOption {
+              type = types.path;
+              description = lib.mdDoc "Path to the unlock script";
+            };
+          };
+        });
+        default = [ ];
+        example = literalExpression ''[{ name = "03f0:4e1d"; script = "''${pkgs.modemmanager}/share/ModemManager/fcc-unlock.available.d/03f0:4e1d"; }]'';
         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
-          [the docs](https://modemmanager.org/docs/modemmanager/fcc-unlock/)
-          for more details.
+          List of FCC unlock scripts to enable on the system, behaving as described in
+          https://modemmanager.org/docs/modemmanager/fcc-unlock/#integration-with-third-party-fcc-unlock-tools.
         '';
       };
+      ensureProfiles = {
+        profiles = with lib.types; mkOption {
+          type = attrsOf (submodule {
+            freeformType = ini.type;
+
+            options = {
+              connection = {
+                id = lib.mkOption {
+                  type = str;
+                  description = "This is the name that will be displayed by NetworkManager and GUIs.";
+                };
+                type = lib.mkOption {
+                  type = str;
+                  description = "The connection type defines the connection kind, like vpn, wireguard, gsm, wifi and more.";
+                  example = "vpn";
+                };
+              };
+            };
+          });
+          apply = (lib.filterAttrsRecursive (n: v: v != { }));
+          default = { };
+          example = {
+            home-wifi = {
+              connection = {
+                id = "home-wifi";
+                type = "wifi";
+                permissions = "";
+              };
+              wifi = {
+                mac-address-blacklist = "";
+                mode = "infrastructure";
+                ssid = "Home Wi-Fi";
+              };
+              wifi-security = {
+                auth-alg = "open";
+                key-mgmt = "wpa-psk";
+                psk = "$HOME_WIFI_PASSWORD";
+              };
+              ipv4 = {
+                dns-search = "";
+                method = "auto";
+              };
+              ipv6 = {
+                addr-gen-mode = "stable-privacy";
+                dns-search = "";
+                method = "auto";
+              };
+            };
+          };
+          description = lib.mdDoc ''
+            Declaratively define NetworkManager profiles. You can find information about the generated file format [here](https://networkmanager.dev/docs/api/latest/nm-settings-keyfile.html) and [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/assembly_networkmanager-connection-profiles-in-keyfile-format_configuring-and-managing-networking).
+            You current profiles which are most likely stored in `/etc/NetworkManager/system-connections` and there is [a tool](https://github.com/janik-haag/nm2nix) to convert them to the needed nix code.
+            If you add a new ad-hoc connection via a GUI or nmtui or anything similar it should just work together with the declarative ones.
+            And if you edit a declarative profile NetworkManager will move it to the persistent storage and treat it like a ad-hoc one,
+            but there will be two profiles as soon as the systemd unit from this option runs again which can be confusing since NetworkManager tools will start displaying two profiles with the same name and probably a bit different settings depending on what you edited.
+            A profile won't be deleted even if it's removed from the config until the system reboots because that's when NetworkManager clears it's temp directory.
+          '';
+        };
+        environmentFiles = mkOption {
+          default = [];
+          type = types.listOf types.path;
+          example = [ "/run/secrets/network-manager.env" ];
+          description = lib.mdDoc ''
+            Files to load as environment file. Environment variables from this file
+            will be substituted into the static configuration file using [envsubst](https://github.com/a8m/envsubst).
+          '';
+        };
+      };
     };
   };
 
@@ -387,7 +456,14 @@ in {
       [ "networking" "networkmanager" "packages" ]
       [ "networking" "networkmanager" "plugins" ])
     (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ])
-    (mkRemovedOptionModule ["networking" "networkmanager" "dynamicHosts"] ''
+    (mkRemovedOptionModule [ "networking" "networkmanager" "enableFccUnlock" ] ''
+      This option was removed, because using bundled FCC unlock scripts is risky,
+      might conflict with vendor-provided unlock scripts, and should
+      be a conscious decision on a per-device basis.
+      Instead it's recommended to use the
+      `networking.networkmanager.fccUnlockScripts` option.
+    '')
+    (mkRemovedOptionModule [ "networking" "networkmanager" "dynamicHosts" ] ''
       This option was removed because allowing (multiple) regular users to
       override host entries affecting the whole system opens up a huge attack
       vector. There seem to be very rare cases where this might be useful.
@@ -395,6 +471,9 @@ in {
       them via the DNS server in your network, or use environment.etc
       to add a file into /etc/NetworkManager/dnsmasq.d reconfiguring hostsdir.
     '')
+    (mkRemovedOptionModule [ "networking" "networkmanager" "firewallBackend" ] ''
+      This option was removed as NixOS is now using iptables-nftables-compat even when using iptables, therefore Networkmanager now uses the nftables backend unconditionally.
+    '')
   ];
 
 
@@ -403,7 +482,8 @@ in {
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [];
+      {
+        assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [ ];
         message = ''
           You can not use networking.networkmanager with networking.wireless.
           Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
@@ -414,25 +494,29 @@ in {
     hardware.wirelessRegulatoryDatabase = true;
 
     environment.etc = {
-        "NetworkManager/NetworkManager.conf".source = configFile;
-      }
-      // builtins.listToAttrs (map (pkg: nameValuePair "NetworkManager/${pkg.networkManagerPlugin}" {
+      "NetworkManager/NetworkManager.conf".source = configFile;
+    }
+    // builtins.listToAttrs (map
+      (pkg: nameValuePair "NetworkManager/${pkg.networkManagerPlugin}" {
         source = "${pkg}/lib/NetworkManager/${pkg.networkManagerPlugin}";
-      }) cfg.plugins)
-      // optionalAttrs cfg.enableFccUnlock
-         {
-           "ModemManager/fcc-unlock.d".source =
-             "${pkgs.modemmanager}/share/ModemManager/fcc-unlock.available.d/*";
-         }
-      // optionalAttrs (cfg.appendNameservers != [] || cfg.insertNameservers != [])
-         {
-           "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript;
-         }
-      // listToAttrs (lib.imap1 (i: s:
-         {
-            name = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
-            value = { mode = "0544"; inherit (s) source; };
-         }) cfg.dispatcherScripts);
+      })
+      cfg.plugins)
+    // builtins.listToAttrs (map
+      (e: nameValuePair "ModemManager/fcc-unlock.d/${e.id}" {
+        source = e.path;
+      })
+      cfg.fccUnlockScripts)
+    // optionalAttrs (cfg.appendNameservers != [ ] || cfg.insertNameservers != [ ])
+      {
+        "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript;
+      }
+    // listToAttrs (lib.imap1
+      (i: s:
+        {
+          name = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
+          value = { mode = "0544"; inherit (s) source; };
+        })
+      cfg.dispatcherScripts);
 
     environment.systemPackages = packages;
 
@@ -481,7 +565,10 @@ in {
       wantedBy = [ "network-online.target" ];
     };
 
-    systemd.services.ModemManager.aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
+    systemd.services.ModemManager = {
+      aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
+      path = lib.optionals (cfg.fccUnlockScripts != []) [ pkgs.libqmi pkgs.libmbim ];
+    };
 
     systemd.services.NetworkManager-dispatcher = {
       wantedBy = [ "network.target" ];
@@ -492,6 +579,30 @@ in {
       aliases = [ "dbus-org.freedesktop.nm-dispatcher.service" ];
     };
 
+    systemd.services.NetworkManager-ensure-profiles = mkIf (cfg.ensureProfiles.profiles != { }) {
+      description = "Ensure that NetworkManager declarative profiles are created";
+      wantedBy = [ "multi-user.target" ];
+      before = [ "network-online.target" ];
+      script = let
+        path = id: "/run/NetworkManager/system-connections/${id}.nmconnection";
+      in ''
+        mkdir -p /run/NetworkManager/system-connections
+      '' + lib.concatMapStringsSep "\n"
+        (profile: ''
+          ${pkgs.envsubst}/bin/envsubst -i ${ini.generate (lib.escapeShellArg profile.n) profile.v} > ${path (lib.escapeShellArg profile.n)}
+        '') (lib.mapAttrsToList (n: v: { inherit n v; }) cfg.ensureProfiles.profiles)
+      + ''
+        if systemctl is-active --quiet NetworkManager; then
+          ${pkgs.networkmanager}/bin/nmcli connection reload
+        fi
+      '';
+      serviceConfig = {
+        EnvironmentFile = cfg.ensureProfiles.environmentFiles;
+        UMask = "0177";
+        Type = "oneshot";
+      };
+    };
+
     # Turn off NixOS' network management when networking is managed entirely by NetworkManager
     networking = mkMerge [
       (mkIf (!delegateWireless) {
diff --git a/nixos/modules/services/networking/nftables.nix b/nixos/modules/services/networking/nftables.nix
index 0e4cd6fa1503b..424d005dc0b5e 100644
--- a/nixos/modules/services/networking/nftables.nix
+++ b/nixos/modules/services/networking/nftables.nix
@@ -70,6 +70,26 @@ in
       '';
     };
 
+    networking.nftables.checkRulesetRedirects = mkOption {
+      type = types.addCheck (types.attrsOf types.path) (attrs: all types.path.check (attrNames attrs));
+      default = {
+        "/etc/hosts" = config.environment.etc.hosts.source;
+        "/etc/protocols" = config.environment.etc.protocols.source;
+        "/etc/services" = config.environment.etc.services.source;
+      };
+      defaultText = literalExpression ''
+        {
+          "/etc/hosts" = config.environment.etc.hosts.source;
+          "/etc/protocols" = config.environment.etc.protocols.source;
+          "/etc/services" = config.environment.etc.services.source;
+        }
+      '';
+      description = mdDoc ''
+        Set of paths that should be intercepted and rewritten while checking the ruleset
+        using `pkgs.buildPackages.libredirect`.
+      '';
+    };
+
     networking.nftables.preCheckRuleset = mkOption {
       type = types.lines;
       default = "";
@@ -83,7 +103,7 @@ in
       '';
     };
 
-    networking.nftables.flushRuleset = mkEnableOption (lib.mdDoc "Flush the entire ruleset on each reload.");
+    networking.nftables.flushRuleset = mkEnableOption (lib.mdDoc "flushing the entire ruleset on each reload");
 
     networking.nftables.extraDeletions = mkOption {
       type = types.lines;
@@ -228,7 +248,6 @@ in
   config = mkIf cfg.enable {
     boot.blacklistedKernelModules = [ "ip_tables" ];
     environment.systemPackages = [ pkgs.nftables ];
-    networking.networkmanager.firewallBackend = mkDefault "nftables";
     # versionOlder for backportability, remove afterwards
     networking.nftables.flushRuleset = mkDefault (versionOlder config.system.stateVersion "23.11" || (cfg.rulesetFile != null || cfg.ruleset != ""));
     systemd.services.nftables = {
@@ -282,7 +301,7 @@ in
             cp $out ruleset.conf
             sed 's|include "${deletionsScriptVar}"||' -i ruleset.conf
             ${cfg.preCheckRuleset}
-            export NIX_REDIRECTS=/etc/protocols=${pkgs.buildPackages.iana-etc}/etc/protocols:/etc/services=${pkgs.buildPackages.iana-etc}/etc/services
+            export NIX_REDIRECTS=${escapeShellArg (concatStringsSep ":" (mapAttrsToList (n: v: "${n}=${v}") cfg.checkRulesetRedirects))}
             LD_PRELOAD="${pkgs.buildPackages.libredirect}/lib/libredirect.so ${pkgs.buildPackages.lklWithFirewall.lib}/lib/liblkl-hijack.so" \
               ${pkgs.buildPackages.nftables}/bin/nft --check --file ruleset.conf
           '';
diff --git a/nixos/modules/services/networking/ngircd.nix b/nixos/modules/services/networking/ngircd.nix
index 5e721f5aa6258..a2fff78fdff89 100644
--- a/nixos/modules/services/networking/ngircd.nix
+++ b/nixos/modules/services/networking/ngircd.nix
@@ -28,14 +28,7 @@ in {
         type = types.lines;
       };
 
-      package = mkOption {
-        description = lib.mdDoc "The ngircd package.";
-
-        type = types.package;
-
-        default = pkgs.ngircd;
-        defaultText = literalExpression "pkgs.ngircd";
-      };
+      package = mkPackageOption pkgs "ngircd" { };
     };
   };
 
diff --git a/nixos/modules/services/networking/nix-serve.nix b/nixos/modules/services/networking/nix-serve.nix
index f37be31270b7c..a0c0be2ff254f 100644
--- a/nixos/modules/services/networking/nix-serve.nix
+++ b/nixos/modules/services/networking/nix-serve.nix
@@ -26,14 +26,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.nix-serve;
-        defaultText = literalExpression "pkgs.nix-serve";
-        description = lib.mdDoc ''
-          nix-serve package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "nix-serve" { };
 
       openFirewall = mkOption {
         type = types.bool;
@@ -67,6 +60,10 @@ in
   };
 
   config = mkIf cfg.enable {
+    nix.settings = lib.optionalAttrs (lib.versionAtLeast config.nix.package.version "2.4") {
+      extra-allowed-users = [ "nix-serve" ];
+    };
+
     systemd.services.nix-serve = {
       description = "nix-serve binary cache server";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/nomad.nix b/nixos/modules/services/networking/nomad.nix
index b1e51195247a5..8cb0264648de2 100644
--- a/nixos/modules/services/networking/nomad.nix
+++ b/nixos/modules/services/networking/nomad.nix
@@ -10,14 +10,7 @@ in
     services.nomad = {
       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 = lib.mdDoc ''
-          The package used for the Nomad agent and CLI.
-        '';
-      };
+      package = mkPackageOption pkgs "nomad" { };
 
       extraPackages = mkOption {
         type = types.listOf types.package;
diff --git a/nixos/modules/services/networking/ntp/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix
index afd721e34da5a..b56bea4e134f6 100644
--- a/nixos/modules/services/networking/ntp/chrony.nix
+++ b/nixos/modules/services/networking/ntp/chrony.nix
@@ -9,6 +9,7 @@ let
   stateDir = cfg.directory;
   driftFile = "${stateDir}/chrony.drift";
   keyFile = "${stateDir}/chrony.keys";
+  rtcFile = "${stateDir}/chrony.rtc";
 
   configFile = pkgs.writeText "chrony.conf" ''
     ${concatMapStringsSep "\n" (server: "server " + server + " " + cfg.serverOption + optionalString (cfg.enableNTS) " nts") cfg.servers}
@@ -20,8 +21,10 @@ let
 
     driftfile ${driftFile}
     keyfile ${keyFile}
+    ${optionalString (cfg.enableRTCTrimming) "rtcfile ${rtcFile}"}
     ${optionalString (cfg.enableNTS) "ntsdumpdir ${stateDir}"}
 
+    ${optionalString (cfg.enableRTCTrimming) "rtcautotrim ${builtins.toString cfg.autotrimThreshold}"}
     ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
 
     ${cfg.extraConfig}
@@ -44,14 +47,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.chrony;
-        defaultText = literalExpression "pkgs.chrony";
-        description = lib.mdDoc ''
-          Which chrony package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "chrony" { };
 
       servers = mkOption {
         default = config.networking.timeServers;
@@ -85,6 +81,33 @@ in
         '';
       };
 
+      enableRTCTrimming = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Enable tracking of the RTC offset to the system clock and automatic trimming.
+          See also [](#opt-services.chrony.autotrimThreshold)
+
+          ::: {.note}
+          This is not compatible with the `rtcsync` directive, which naively syncs the RTC time every 11 minutes.
+
+          Tracking the RTC drift will allow more precise timekeeping,
+          especially on intermittently running devices, where the RTC is very relevant.
+          :::
+        '';
+      };
+
+      autotrimThreshold = mkOption {
+        type = types.ints.positive;
+        default = 30;
+        example = 10;
+        description = ''
+          Maximum estimated error threshold for the `rtcautotrim` command.
+          When reached, the RTC will be trimmed.
+          Only used when [](#opt-services.chrony.enableRTCTrimming) is enabled.
+        '';
+      };
+
       enableNTS = mkOption {
         type = types.bool;
         default = false;
@@ -132,7 +155,7 @@ in
       };
 
       extraFlags = mkOption {
-        default = [];
+        default = [ ];
         example = [ "-s" ];
         type = types.listOf types.str;
         description = lib.mdDoc "Extra flags passed to the chronyd command.";
@@ -141,14 +164,15 @@ in
   };
 
   config = mkIf cfg.enable {
-    meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+    meta.maintainers = with lib.maintainers; [ thoughtpolice vifino ];
 
     environment.systemPackages = [ chronyPkg ];
 
     users.groups.chrony.gid = config.ids.gids.chrony;
 
     users.users.chrony =
-      { uid = config.ids.uids.chrony;
+      {
+        uid = config.ids.uids.chrony;
         group = "chrony";
         description = "chrony daemon user";
         home = stateDir;
@@ -156,21 +180,29 @@ in
 
     services.timesyncd.enable = mkForce false;
 
+    # If chrony controls and tracks the RTC, writing it externally causes clock error.
+    systemd.services.save-hwclock = lib.mkIf cfg.enableRTCTrimming {
+      enable = lib.mkForce false;
+    };
+
     systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; };
 
     systemd.tmpfiles.rules = [
       "d ${stateDir} 0750 chrony chrony - -"
       "f ${driftFile} 0640 chrony chrony - -"
       "f ${keyFile} 0640 chrony chrony - -"
+    ] ++ lib.optionals cfg.enableRTCTrimming [
+      "f ${rtcFile} 0640 chrony chrony - -"
     ];
 
     systemd.services.chronyd =
-      { description = "chrony NTP daemon";
+      {
+        description = "chrony NTP daemon";
 
         wantedBy = [ "multi-user.target" ];
-        wants    = [ "time-sync.target" ];
-        before   = [ "time-sync.target" ];
-        after    = [ "network.target" "nss-lookup.target" ];
+        wants = [ "time-sync.target" ];
+        before = [ "time-sync.target" ];
+        after = [ "network.target" "nss-lookup.target" ];
         conflicts = [ "ntpd.service" "systemd-timesyncd.service" ];
 
         path = [ chronyPkg ];
@@ -218,5 +250,18 @@ in
           SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources" "@clock" "@setuid" "capset" "@chown" ];
         };
       };
+
+    assertions = [
+      {
+        assertion = !(cfg.enableRTCTrimming && builtins.any (line: (builtins.match "^ *rtcsync" line) != null) (lib.strings.splitString "\n" cfg.extraConfig));
+        message = ''
+          The chrony module now configures `rtcfile` and `rtcautotrim` for you.
+          These options conflict with `rtcsync` and cause chrony to crash.
+          Unless you are very sure the former isn't what you want, please remove
+          `rtcsync` from `services.chrony.extraConfig`.
+          Alternatively, disable this behaviour by `services.chrony.enableRTCTrimming = false;`
+        '';
+      }
+    ];
   };
 }
diff --git a/nixos/modules/services/networking/ntp/ntpd-rs.nix b/nixos/modules/services/networking/ntp/ntpd-rs.nix
new file mode 100644
index 0000000000000..4643ac146ddb9
--- /dev/null
+++ b/nixos/modules/services/networking/ntp/ntpd-rs.nix
@@ -0,0 +1,89 @@
+{ lib, config, pkgs, ... }:
+
+let
+  cfg = config.services.ntpd-rs;
+  format = pkgs.formats.toml { };
+  configFile = format.generate "ntpd-rs.toml" cfg.settings;
+in
+{
+  options.services.ntpd-rs = {
+    enable = lib.mkEnableOption "Network Time Service (ntpd-rs)";
+    metrics.enable = lib.mkEnableOption "ntpd-rs Prometheus Metrics Exporter";
+
+    package = lib.mkPackageOption pkgs "ntpd-rs" { };
+
+    useNetworkingTimeServers = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Use source time servers from {var}`networking.timeServers` in config.
+      '';
+    };
+
+    settings = lib.mkOption {
+      type = lib.types.submodule {
+        freeformType = format.type;
+      };
+      default = { };
+      description = lib.mdDoc ''
+        Settings to write to {file}`ntp.toml`
+
+        See <https://docs.ntpd-rs.pendulum-project.org/man/ntp.toml.5>
+        for more information about available options.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !config.services.timesyncd.enable;
+        message = ''
+          `ntpd-rs` is not compatible with `services.timesyncd`. Please disable one of them.
+        '';
+      }
+    ];
+
+    environment.systemPackages = [ cfg.package ];
+    systemd.packages = [ cfg.package ];
+
+    services.timesyncd.enable = false;
+    systemd.services.systemd-timedated.environment = {
+      SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd-rs.service";
+    };
+
+    services.ntpd-rs.settings = {
+      observability = {
+        observation-path = lib.mkDefault "/var/run/ntpd-rs/observe";
+      };
+      source = lib.mkIf cfg.useNetworkingTimeServers (map
+        (ts: {
+          mode = "server";
+          address = ts;
+        })
+        config.networking.timeServers);
+    };
+
+    systemd.services.ntpd-rs = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = "";
+        Group = "";
+        DynamicUser = true;
+        ExecStart = [ "" "${lib.makeBinPath [ cfg.package ]}/ntp-daemon --config=${configFile}" ];
+      };
+    };
+
+    systemd.services.ntpd-rs-metrics = lib.mkIf cfg.metrics.enable {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = "";
+        Group = "";
+        DynamicUser = true;
+        ExecStart = [ "" "${lib.makeBinPath [ cfg.package ]}/ntp-metrics-exporter --config=${configFile}" ];
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ fpletz ];
+}
diff --git a/nixos/modules/services/networking/ntp/ntpd.nix b/nixos/modules/services/networking/ntp/ntpd.nix
index 036a8df635db0..2bc690cacf096 100644
--- a/nixos/modules/services/networking/ntp/ntpd.nix
+++ b/nixos/modules/services/networking/ntp/ntpd.nix
@@ -56,7 +56,7 @@ in
           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
-          http://support.ntp.org/bin/view/Support/AccessRestrictions
+          https://support.ntp.org/Support/AccessRestrictions
         '';
         default = [ "limited" "kod" "nomodify" "notrap" "noquery" "nopeer" ];
       };
diff --git a/nixos/modules/services/networking/ocserv.nix b/nixos/modules/services/networking/ocserv.nix
index 9548fd92dbda3..3c61d56b893e9 100644
--- a/nixos/modules/services/networking/ocserv.nix
+++ b/nixos/modules/services/networking/ocserv.nix
@@ -85,6 +85,7 @@ in
     systemd.services.ocserv = {
       description = "OpenConnect SSL VPN server";
       documentation = [ "man:ocserv(8)" ];
+      wants = [ "network-online.target" ];
       after = [ "dbus.service" "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
 
diff --git a/nixos/modules/services/networking/openconnect.nix b/nixos/modules/services/networking/openconnect.nix
index 7f9006053b890..d2730faf9381c 100644
--- a/nixos/modules/services/networking/openconnect.nix
+++ b/nixos/modules/services/networking/openconnect.nix
@@ -117,7 +117,7 @@ let
   };
 in {
   options.networking.openconnect = {
-    package = mkPackageOptionMD pkgs "openconnect" { };
+    package = mkPackageOption pkgs "openconnect" { };
 
     interfaces = mkOption {
       description = lib.mdDoc "OpenConnect interfaces.";
diff --git a/nixos/modules/services/networking/peroxide.nix b/nixos/modules/services/networking/peroxide.nix
index 885ee1d96cd05..34c82e2c8b039 100644
--- a/nixos/modules/services/networking/peroxide.nix
+++ b/nixos/modules/services/networking/peroxide.nix
@@ -11,7 +11,7 @@ in
   options.services.peroxide = {
     enable = mkEnableOption (lib.mdDoc "peroxide");
 
-    package = mkPackageOptionMD pkgs "peroxide" {
+    package = mkPackageOption pkgs "peroxide" {
       default = [ "peroxide" ];
     };
 
diff --git a/nixos/modules/services/networking/pixiecore.nix b/nixos/modules/services/networking/pixiecore.nix
index f410be4716466..1f47a1d0b631d 100644
--- a/nixos/modules/services/networking/pixiecore.nix
+++ b/nixos/modules/services/networking/pixiecore.nix
@@ -16,7 +16,7 @@ in
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Open ports (67, 69 UDP and 4011, 'port', 'statusPort' TCP) in the firewall for Pixiecore.
+          Open ports (67, 69, 4011 UDP and 'port', 'statusPort' TCP) in the firewall for Pixiecore.
         '';
       };
 
@@ -103,8 +103,8 @@ in
     };
 
     networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ 4011 cfg.port cfg.statusPort ];
-      allowedUDPPorts = [ 67 69 ];
+      allowedTCPPorts = [ cfg.port cfg.statusPort ];
+      allowedUDPPorts = [ 67 69 4011 ];
     };
 
     systemd.services.pixiecore = {
diff --git a/nixos/modules/services/networking/pleroma.nix b/nixos/modules/services/networking/pleroma.nix
index e9db7f3eab8ed..8470f5e9cbc0c 100644
--- a/nixos/modules/services/networking/pleroma.nix
+++ b/nixos/modules/services/networking/pleroma.nix
@@ -6,12 +6,7 @@ in {
     services.pleroma = with lib; {
       enable = mkEnableOption (lib.mdDoc "pleroma");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.pleroma;
-        defaultText = literalExpression "pkgs.pleroma";
-        description = lib.mdDoc "Pleroma package to use.";
-      };
+      package = mkPackageOption pkgs "pleroma" { };
 
       user = mkOption {
         type = types.str;
@@ -97,6 +92,7 @@ in {
 
     systemd.services.pleroma = {
       description = "Pleroma social network";
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" "postgresql.service" ];
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ config.environment.etc."/pleroma/config.exs".source ];
@@ -146,6 +142,6 @@ in {
     };
 
   };
-  meta.maintainers = with lib.maintainers; [ ninjatrappeur ];
+  meta.maintainers = with lib.maintainers; [ picnoir ];
   meta.doc = ./pleroma.md;
 }
diff --git a/nixos/modules/services/networking/pppd.nix b/nixos/modules/services/networking/pppd.nix
index 75fc04c675717..855b5358f47f6 100644
--- a/nixos/modules/services/networking/pppd.nix
+++ b/nixos/modules/services/networking/pppd.nix
@@ -14,12 +14,7 @@ in
     services.pppd = {
       enable = mkEnableOption (lib.mdDoc "pppd");
 
-      package = mkOption {
-        default = pkgs.ppp;
-        defaultText = literalExpression "pkgs.ppp";
-        type = types.package;
-        description = lib.mdDoc "pppd package to use.";
-      };
+      package = mkPackageOption pkgs "ppp" { };
 
       peers = mkOption {
         default = {};
diff --git a/nixos/modules/services/networking/prayer.nix b/nixos/modules/services/networking/prayer.nix
deleted file mode 100644
index 197aa8a6f4480..0000000000000
--- a/nixos/modules/services/networking/prayer.nix
+++ /dev/null
@@ -1,90 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  inherit (pkgs) prayer;
-
-  cfg = config.services.prayer;
-
-  stateDir = "/var/lib/prayer";
-
-  prayerUser = "prayer";
-  prayerGroup = "prayer";
-
-  prayerExtraCfg = pkgs.writeText "extraprayer.cf" ''
-    prefix = "${prayer}"
-    var_prefix = "${stateDir}"
-    prayer_user = "${prayerUser}"
-    prayer_group = "${prayerGroup}"
-    sendmail_path = "/run/wrappers/bin/sendmail"
-
-    use_http_port ${cfg.port}
-
-    ${cfg.extraConfig}
-  '';
-
-  prayerCfg = pkgs.runCommand "prayer.cf" { preferLocalBuild = true; } ''
-    # We have to remove the http_port 80, or it will start a server there
-    cat ${prayer}/etc/prayer.cf | grep -v http_port > $out
-    cat ${prayerExtraCfg} >> $out
-  '';
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.prayer = {
-
-      enable = mkEnableOption (lib.mdDoc "the prayer webmail http server");
-
-      port = mkOption {
-        default = 2080;
-        type = types.port;
-        description = lib.mdDoc ''
-          Port the prayer http server is listening to.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "" ;
-        description = lib.mdDoc ''
-          Extra configuration. Contents will be added verbatim to the configuration file.
-        '';
-      };
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.prayer.enable {
-    environment.systemPackages = [ prayer ];
-
-    users.users.${prayerUser} =
-      { uid = config.ids.uids.prayer;
-        description = "Prayer daemon user";
-        home = stateDir;
-      };
-
-    users.groups.${prayerGroup} =
-      { gid = config.ids.gids.prayer; };
-
-    systemd.services.prayer = {
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig.Type = "forking";
-      preStart = ''
-        mkdir -m 0755 -p ${stateDir}
-        chown ${prayerUser}:${prayerGroup} ${stateDir}
-      '';
-      script = "${prayer}/sbin/prayer --config-file=${prayerCfg}";
-    };
-  };
-}
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index 0066c77438f41..2952df2a10993 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -496,12 +496,8 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        description = lib.mdDoc "Prosody package to use";
-        default = pkgs.prosody;
-        defaultText = literalExpression "pkgs.prosody";
-        example = literalExpression ''
+      package = mkPackageOption pkgs "prosody" {
+        example = ''
           pkgs.prosody.override {
             withExtraLibs = [ pkgs.luaPackages.lpty ];
             withCommunityModules = [ "auth_external" ];
@@ -779,9 +775,6 @@ in
 
       admins = ${toLua cfg.admins}
 
-      -- we already build with libevent, so we can just enable it for a more performant server
-      use_libevent = true
-
       modules_enabled = {
 
         ${ lib.concatStringsSep "\n  " (lib.mapAttrsToList
diff --git a/nixos/modules/services/networking/quassel.nix b/nixos/modules/services/networking/quassel.nix
index a074023b5ee40..4294d67fffd31 100644
--- a/nixos/modules/services/networking/quassel.nix
+++ b/nixos/modules/services/networking/quassel.nix
@@ -35,14 +35,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.quasselDaemon;
-        defaultText = literalExpression "pkgs.quasselDaemon";
-        description = lib.mdDoc ''
-          The package of the quassel daemon.
-        '';
-      };
+      package = mkPackageOption pkgs "quasselDaemon" { };
 
       interfaces = mkOption {
         type = types.listOf types.str;
diff --git a/nixos/modules/services/networking/quicktun.nix b/nixos/modules/services/networking/quicktun.nix
index 7aed972adc882..2d44659f20804 100644
--- a/nixos/modules/services/networking/quicktun.nix
+++ b/nixos/modules/services/networking/quicktun.nix
@@ -1,94 +1,153 @@
-{ config, pkgs, lib, ... }:
+{ options, config, pkgs, lib, ... }:
 
 let
+  inherit (lib) mkOption mdDoc types mkIf;
 
+  opt = options.services.quicktun;
   cfg = config.services.quicktun;
-
 in
-
-with lib;
-
 {
   options = {
-
     services.quicktun = mkOption {
       default = { };
-      description = lib.mdDoc "QuickTun tunnels";
-      type = types.attrsOf (types.submodule {
+      description = mdDoc ''
+        QuickTun tunnels.
+
+        See <http://wiki.ucis.nl/QuickTun> for more information about available options.
+      '';
+      type = types.attrsOf (types.submodule ({ name, ... }: let
+        qtcfg = cfg.${name};
+      in {
         options = {
           tunMode = mkOption {
-            type = types.int;
-            default = 0;
-            example = 1;
-            description = lib.mdDoc "";
+            type = with types; coercedTo bool (b: if b then 1 else 0) (ints.between 0 1);
+            default = false;
+            example = true;
+            description = mdDoc "Whether to operate in tun (IP) or tap (Ethernet) mode.";
           };
 
           remoteAddress = mkOption {
             type = types.str;
+            default = "0.0.0.0";
             example = "tunnel.example.com";
-            description = lib.mdDoc "";
+            description = mdDoc ''
+              IP address or hostname of the remote end (use `0.0.0.0` for a floating/dynamic remote endpoint).
+            '';
           };
 
           localAddress = mkOption {
-            type = types.str;
+            type = with types; nullOr str;
+            default = null;
             example = "0.0.0.0";
-            description = lib.mdDoc "";
+            description = mdDoc "IP address or hostname of the local end.";
           };
 
           localPort = mkOption {
-            type = types.int;
+            type = types.port;
             default = 2998;
-            description = lib.mdDoc "";
+            description = mdDoc "Local UDP port.";
           };
 
           remotePort = mkOption {
-            type = types.int;
-            default = 2998;
-            description = lib.mdDoc "";
+            type = types.port;
+            default = qtcfg.localPort;
+            defaultText = lib.literalExpression "config.services.quicktun.<name>.localPort";
+            description = mdDoc " remote UDP port";
           };
 
           remoteFloat = mkOption {
-            type = types.int;
-            default = 0;
-            description = lib.mdDoc "";
+            type = with types; coercedTo bool (b: if b then 1 else 0) (ints.between 0 1);
+            default = false;
+            example = true;
+            description = mdDoc ''
+              Whether to allow the remote address and port to change when properly encrypted packets are received.
+            '';
           };
 
           protocol = mkOption {
-            type = types.str;
+            type = types.enum [ "raw" "nacl0" "nacltai" "salty" ];
             default = "nacltai";
-            description = lib.mdDoc "";
+            description = mdDoc "Which protocol to use.";
           };
 
           privateKey = mkOption {
-            type = types.str;
-            description = lib.mdDoc "";
+            type = with types; nullOr str;
+            default = null;
+            description = mdDoc ''
+              Local secret key in hexadecimal form.
+
+              ::: {.warning}
+              This option is deprecated. Please use {var}`services.quicktun.<name>.privateKeyFile` instead.
+              :::
+
+              ::: {.note}
+              Not needed when {var}`services.quicktun.<name>.protocol` is set to `raw`.
+              :::
+            '';
+          };
+
+          privateKeyFile = mkOption {
+            type = with types; nullOr path;
+            # This is a hack to deprecate `privateKey` without using `mkChangedModuleOption`
+            default = if qtcfg.privateKey == null then null else pkgs.writeText "quickttun-key-${name}" qtcfg.privateKey;
+            defaultText = "null";
+            description = mdDoc ''
+              Path to file containing local secret key in binary or hexadecimal form.
+
+              ::: {.note}
+              Not needed when {var}`services.quicktun.<name>.protocol` is set to `raw`.
+              :::
+            '';
           };
 
           publicKey = mkOption {
-            type = types.str;
-            description = lib.mdDoc "";
+            type = with types; nullOr str;
+            default = null;
+            description = mdDoc ''
+              Remote public key in hexadecimal form.
+
+              ::: {.note}
+              Not needed when {var}`services.quicktun.<name>.protocol` is set to `raw`.
+              :::
+            '';
           };
 
           timeWindow = mkOption {
-            type = types.int;
+            type = types.ints.unsigned;
             default = 5;
-            description = lib.mdDoc "";
+            description = mdDoc ''
+              Allowed time window for first received packet in seconds (positive number allows packets from history)
+            '';
           };
 
           upScript = mkOption {
-            type = types.lines;
-            default = "";
-            description = lib.mdDoc "";
+            type = with types; nullOr lines;
+            default = null;
+            description = mdDoc ''
+              Run specified command or script after the tunnel device has been opened.
+            '';
           };
         };
-      });
+      }));
     };
-
   };
 
-  config = mkIf (cfg != []) {
-    systemd.services = foldr (a: b: a // b) {} (
-      mapAttrsToList (name: qtcfg: {
+  config = {
+    warnings = lib.pipe cfg [
+      (lib.mapAttrsToList (name: value: if value.privateKey != null then name else null))
+      (builtins.filter (n: n != null))
+      (map (n: "  - services.quicktun.${n}.privateKey"))
+      (services: lib.optional (services != [ ]) ''
+        `services.quicktun.<name>.privateKey` is deprecated.
+        Please use `services.quicktun.<name>.privateKeyFile` instead.
+
+        Offending options:
+        ${lib.concatStringsSep "\n" services}
+      '')
+    ];
+
+    systemd.services = lib.mkMerge (
+      lib.mapAttrsToList (name: qtcfg: {
         "quicktun-${name}" = {
           wantedBy = [ "multi-user.target" ];
           after = [ "network.target" ];
@@ -96,14 +155,14 @@ with lib;
             INTERFACE = name;
             TUN_MODE = toString qtcfg.tunMode;
             REMOTE_ADDRESS = qtcfg.remoteAddress;
-            LOCAL_ADDRESS = qtcfg.localAddress;
+            LOCAL_ADDRESS = mkIf (qtcfg.localAddress != null) (qtcfg.localAddress);
             LOCAL_PORT = toString qtcfg.localPort;
             REMOTE_PORT = toString qtcfg.remotePort;
             REMOTE_FLOAT = toString qtcfg.remoteFloat;
-            PRIVATE_KEY = qtcfg.privateKey;
-            PUBLIC_KEY = qtcfg.publicKey;
+            PRIVATE_KEY_FILE = mkIf (qtcfg.privateKeyFile != null) qtcfg.privateKeyFile;
+            PUBLIC_KEY = mkIf (qtcfg.publicKey != null) qtcfg.publicKey;
             TIME_WINDOW = toString qtcfg.timeWindow;
-            TUN_UP_SCRIPT = pkgs.writeScript "quicktun-${name}-up.sh" qtcfg.upScript;
+            TUN_UP_SCRIPT = mkIf (qtcfg.upScript != null) (pkgs.writeScript "quicktun-${name}-up.sh" qtcfg.upScript);
             SUID = "nobody";
           };
           serviceConfig = {
@@ -114,5 +173,4 @@ with lib;
       }) cfg
     );
   };
-
 }
diff --git a/nixos/modules/services/networking/radvd.nix b/nixos/modules/services/networking/radvd.nix
index 72590eda4ee68..57aa212870503 100644
--- a/nixos/modules/services/networking/radvd.nix
+++ b/nixos/modules/services/networking/radvd.nix
@@ -32,14 +32,7 @@ in
         '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.radvd;
-      defaultText = literalExpression "pkgs.radvd";
-      description = lib.mdDoc ''
-        The RADVD package to use for the RADVD service.
-      '';
-    };
+    package = mkPackageOption pkgs "radvd" { };
 
     config = mkOption {
       type = types.lines;
diff --git a/nixos/modules/services/networking/rosenpass.nix b/nixos/modules/services/networking/rosenpass.nix
new file mode 100644
index 0000000000000..487cb6f601429
--- /dev/null
+++ b/nixos/modules/services/networking/rosenpass.nix
@@ -0,0 +1,234 @@
+{ config
+, lib
+, options
+, pkgs
+, ...
+}:
+let
+  inherit (lib)
+    attrValues
+    concatLines
+    concatMap
+    filter
+    filterAttrsRecursive
+    flatten
+    getExe
+    mdDoc
+    mkIf
+    optional
+    ;
+
+  cfg = config.services.rosenpass;
+  opt = options.services.rosenpass;
+  settingsFormat = pkgs.formats.toml { };
+in
+{
+  options.services.rosenpass =
+    let
+      inherit (lib)
+        literalExpression
+        mdDoc
+        mkOption
+        ;
+      inherit (lib.types)
+        enum
+        listOf
+        nullOr
+        path
+        str
+        submodule
+        ;
+    in
+    {
+      enable = lib.mkEnableOption (mdDoc "Rosenpass");
+
+      package = lib.mkPackageOption pkgs "rosenpass" { };
+
+      defaultDevice = mkOption {
+        type = nullOr str;
+        description = mdDoc "Name of the network interface to use for all peers by default.";
+        example = "wg0";
+      };
+
+      settings = mkOption {
+        type = submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            public_key = mkOption {
+              type = path;
+              description = mdDoc "Path to a file containing the public key of the local Rosenpass peer. Generate this by running {command}`rosenpass gen-keys`.";
+            };
+
+            secret_key = mkOption {
+              type = path;
+              description = mdDoc "Path to a file containing the secret key of the local Rosenpass peer. Generate this by running {command}`rosenpass gen-keys`.";
+            };
+
+            listen = mkOption {
+              type = listOf str;
+              description = mdDoc "List of local endpoints to listen for connections.";
+              default = [ ];
+              example = literalExpression "[ \"0.0.0.0:10000\" ]";
+            };
+
+            verbosity = mkOption {
+              type = enum [ "Verbose" "Quiet" ];
+              default = "Quiet";
+              description = mdDoc "Verbosity of output produced by the service.";
+            };
+
+            peers =
+              let
+                peer = submodule {
+                  freeformType = settingsFormat.type;
+
+                  options = {
+                    public_key = mkOption {
+                      type = path;
+                      description = mdDoc "Path to a file containing the public key of the remote Rosenpass peer.";
+                    };
+
+                    endpoint = mkOption {
+                      type = nullOr str;
+                      default = null;
+                      description = mdDoc "Endpoint of the remote Rosenpass peer.";
+                    };
+
+                    device = mkOption {
+                      type = str;
+                      default = cfg.defaultDevice;
+                      defaultText = literalExpression "config.${opt.defaultDevice}";
+                      description = mdDoc "Name of the local WireGuard interface to use for this peer.";
+                    };
+
+                    peer = mkOption {
+                      type = str;
+                      description = mdDoc "WireGuard public key corresponding to the remote Rosenpass peer.";
+                    };
+                  };
+                };
+              in
+              mkOption {
+                type = listOf peer;
+                description = mdDoc "List of peers to exchange keys with.";
+                default = [ ];
+              };
+          };
+        };
+        default = { };
+        description = mdDoc "Configuration for Rosenpass, see <https://rosenpass.eu/> for further information.";
+      };
+    };
+
+  config = mkIf cfg.enable {
+    warnings =
+      let
+        # NOTE: In the descriptions below, we tried to refer to e.g.
+        # options.systemd.network.netdevs."<name>".wireguardPeers.*.PublicKey
+        # directly, but don't know how to traverse "<name>" and * in this path.
+        extractions = [
+          {
+            relevant = config.systemd.network.enable;
+            root = config.systemd.network.netdevs;
+            peer = (x: x.wireguardPeers);
+            key = (x: if x.wireguardPeerConfig ? PublicKey then x.wireguardPeerConfig.PublicKey else null);
+            description = mdDoc "${options.systemd.network.netdevs}.\"<name>\".wireguardPeers.*.wireguardPeerConfig.PublicKey";
+          }
+          {
+            relevant = config.networking.wireguard.enable;
+            root = config.networking.wireguard.interfaces;
+            peer = (x: x.peers);
+            key = (x: x.publicKey);
+            description = mdDoc "${options.networking.wireguard.interfaces}.\"<name>\".peers.*.publicKey";
+          }
+          rec {
+            relevant = root != { };
+            root = config.networking.wg-quick.interfaces;
+            peer = (x: x.peers);
+            key = (x: x.publicKey);
+            description = mdDoc "${options.networking.wg-quick.interfaces}.\"<name>\".peers.*.publicKey";
+          }
+        ];
+        relevantExtractions = filter (x: x.relevant) extractions;
+        extract = { root, peer, key, ... }:
+          filter (x: x != null) (flatten (concatMap (x: (map key (peer x))) (attrValues root)));
+        configuredKeys = flatten (map extract relevantExtractions);
+        itemize = xs: concatLines (map (x: " - ${x}") xs);
+        descriptions = map (x: "`${x.description}`");
+        missingKeys = filter (key: !builtins.elem key configuredKeys) (map (x: x.peer) cfg.settings.peers);
+        unusual = ''
+          While this may work as expected, e.g. you want to manually configure WireGuard,
+          such a scenario is unusual. Please double-check your configuration.
+        '';
+      in
+      (optional (relevantExtractions != [ ] && missingKeys != [ ]) ''
+        You have configured Rosenpass peers with the WireGuard public keys:
+        ${itemize missingKeys}
+        But there is no corresponding active Wireguard peer configuration in any of:
+        ${itemize (descriptions relevantExtractions)}
+        ${unusual}
+      '')
+      ++
+      optional (relevantExtractions == [ ]) ''
+        You have configured Rosenpass, but you have not configured Wireguard via any of:
+        ${itemize (descriptions extractions)}
+        ${unusual}
+      '';
+
+    environment.systemPackages = [ cfg.package pkgs.wireguard-tools ];
+
+    systemd.services.rosenpass =
+      let
+        filterNonNull = filterAttrsRecursive (_: v: v != null);
+        config = settingsFormat.generate "config.toml" (
+          filterNonNull (cfg.settings
+            //
+            (
+              let
+                credentialPath = id: "$CREDENTIALS_DIRECTORY/${id}";
+                # NOTE: We would like to remove all `null` values inside `cfg.settings`
+                # recursively, since `settingsFormat.generate` cannot handle `null`.
+                # This would require to traverse both attribute sets and lists recursively.
+                # `filterAttrsRecursive` only recurses into attribute sets, but not
+                # into values that might contain other attribute sets (such as lists,
+                # e.g. `cfg.settings.peers`). Here, we just specialize on `cfg.settings.peers`,
+                # and this may break unexpectedly whenever a `null` value is contained
+                # in a list in `cfg.settings`, other than `cfg.settings.peers`.
+                peersWithoutNulls = map filterNonNull cfg.settings.peers;
+              in
+              {
+                secret_key = credentialPath "pqsk";
+                public_key = credentialPath "pqpk";
+                peers = peersWithoutNulls;
+              }
+            )
+          )
+        );
+      in
+      rec {
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "network-online.target" ];
+        after = [ "network-online.target" ];
+        path = [ cfg.package pkgs.wireguard-tools ];
+
+        serviceConfig = {
+          User = "rosenpass";
+          Group = "rosenpass";
+          RuntimeDirectory = "rosenpass";
+          DynamicUser = true;
+          AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+          LoadCredential = [
+            "pqsk:${cfg.settings.secret_key}"
+            "pqpk:${cfg.settings.public_key}"
+          ];
+        };
+
+        # See <https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers>
+        environment.CONFIG = "%t/${serviceConfig.RuntimeDirectory}/config.toml";
+
+        preStart = "${getExe pkgs.envsubst} -i ${config} -o \"$CONFIG\"";
+        script = "rosenpass exchange-config \"$CONFIG\"";
+      };
+  };
+}
diff --git a/nixos/modules/services/networking/routedns.nix b/nixos/modules/services/networking/routedns.nix
index 2a29a06700cef..126539702438f 100644
--- a/nixos/modules/services/networking/routedns.nix
+++ b/nixos/modules/services/networking/routedns.nix
@@ -52,12 +52,7 @@ in
       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.";
-    };
+    package = mkPackageOption pkgs "routedns" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/networking/rxe.nix b/nixos/modules/services/networking/rxe.nix
index 7dbb4823b4bcd..07437ed71195b 100644
--- a/nixos/modules/services/networking/rxe.nix
+++ b/nixos/modules/services/networking/rxe.nix
@@ -33,7 +33,7 @@ in {
 
       wantedBy = [ "multi-user.target" ];
       after = [ "systemd-modules-load.service" "network-online.target" ];
-      wants = [ "network-pre.target" ];
+      wants = [ "network-pre.target" "network-online.target" ];
 
       serviceConfig = {
         Type = "oneshot";
diff --git a/nixos/modules/services/networking/sabnzbd.nix b/nixos/modules/services/networking/sabnzbd.nix
index 8f3545df89950..cff2622b38e90 100644
--- a/nixos/modules/services/networking/sabnzbd.nix
+++ b/nixos/modules/services/networking/sabnzbd.nix
@@ -17,12 +17,7 @@ in
     services.sabnzbd = {
       enable = mkEnableOption (lib.mdDoc "the sabnzbd server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.sabnzbd;
-        defaultText = lib.literalExpression "pkgs.sabnzbd";
-        description = lib.mdDoc "The sabnzbd executable package run by the service.";
-      };
+      package = mkPackageOption pkgs "sabnzbd" { };
 
       configFile = mkOption {
         type = types.path;
diff --git a/nixos/modules/services/networking/seafile.nix b/nixos/modules/services/networking/seafile.nix
index b07d51b9b49a0..9caabc60c78fb 100644
--- a/nixos/modules/services/networking/seafile.nix
+++ b/nixos/modules/services/networking/seafile.nix
@@ -121,12 +121,7 @@ in {
       '';
     };
 
-    seafilePackage = mkOption {
-      type = types.package;
-      description = lib.mdDoc "Which package to use for the seafile server.";
-      default = pkgs.seafile-server;
-      defaultText = literalExpression "pkgs.seafile-server";
-    };
+    seafilePackage = mkPackageOption pkgs "seafile-server" { };
 
     seahubExtraConf = mkOption {
       default = "";
diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix
index 40648c724812b..938d585e31793 100644
--- a/nixos/modules/services/networking/searx.nix
+++ b/nixos/modules/services/networking/searx.nix
@@ -43,12 +43,8 @@ in
       [ "services" "searx" "settingsFile" ])
   ];
 
-  ###### interface
-
   options = {
-
     services.searx = {
-
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -147,12 +143,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.searx;
-        defaultText = literalExpression "pkgs.searx";
-        description = lib.mdDoc "searx package to use.";
-      };
+      package = mkPackageOption pkgs "searxng" { };
 
       runInUwsgi = mkOption {
         type = types.bool;
@@ -190,21 +181,7 @@ in
 
   };
 
-
-  ###### implementation
-
   config = mkIf cfg.enable {
-    assertions = [
-      {
-        assertion = (cfg.limiterSettings != { }) -> cfg.package.pname == "searxng";
-        message = "services.searx.limiterSettings requires services.searx.package to be searxng.";
-      }
-      {
-        assertion = cfg.redisCreateLocally -> cfg.package.pname == "searxng";
-        message = "services.searx.redisCreateLocally requires services.searx.package to be searxng.";
-      }
-    ];
-
     environment.systemPackages = [ cfg.package ];
 
     users.users.searx =
@@ -245,10 +222,10 @@ in
       };
     };
 
-    systemd.services.uwsgi = mkIf (cfg.runInUwsgi)
-      { requires = [ "searx-init.service" ];
-        after = [ "searx-init.service" ];
-      };
+    systemd.services.uwsgi = mkIf cfg.runInUwsgi {
+      requires = [ "searx-init.service" ];
+      after = [ "searx-init.service" ];
+    };
 
     services.searx.settings = {
       # merge NixOS settings with defaults settings.yml
@@ -256,7 +233,7 @@ in
       redis.url = lib.mkIf cfg.redisCreateLocally "unix://${config.services.redis.servers.searx.unixSocket}";
     };
 
-    services.uwsgi = mkIf (cfg.runInUwsgi) {
+    services.uwsgi = mkIf cfg.runInUwsgi {
       enable = true;
       plugins = [ "python3" ];
 
@@ -270,6 +247,7 @@ in
         enable-threads = true;
         module = "searx.webapp";
         env = [
+          # TODO: drop this as it is only required for searx
           "SEARX_SETTINGS_PATH=${cfg.settingsFile}"
           # searxng compatibility https://github.com/searxng/searxng/issues/1519
           "SEARXNG_SETTINGS_PATH=${cfg.settingsFile}"
diff --git a/nixos/modules/services/networking/shellhub-agent.nix b/nixos/modules/services/networking/shellhub-agent.nix
index 7cce23cb9c4e3..ad33c50f9d633 100644
--- a/nixos/modules/services/networking/shellhub-agent.nix
+++ b/nixos/modules/services/networking/shellhub-agent.nix
@@ -14,7 +14,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "ShellHub Agent daemon");
 
-      package = mkPackageOptionMD pkgs "shellhub-agent" { };
+      package = mkPackageOption pkgs "shellhub-agent" { };
 
       preferredHostname = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/networking/sing-box.nix b/nixos/modules/services/networking/sing-box.nix
index a884bcd271ec2..ea7363713601e 100644
--- a/nixos/modules/services/networking/sing-box.nix
+++ b/nixos/modules/services/networking/sing-box.nix
@@ -13,7 +13,7 @@ in
     services.sing-box = {
       enable = lib.mkEnableOption (lib.mdDoc "sing-box universal proxy platform");
 
-      package = lib.mkPackageOptionMD pkgs "sing-box" { };
+      package = lib.mkPackageOption pkgs "sing-box" { };
 
       settings = lib.mkOption {
         type = lib.types.submodule {
diff --git a/nixos/modules/services/networking/skydns.nix b/nixos/modules/services/networking/skydns.nix
index 84cf6b0deac11..0514bff2767e6 100644
--- a/nixos/modules/services/networking/skydns.nix
+++ b/nixos/modules/services/networking/skydns.nix
@@ -55,12 +55,7 @@ in {
       example = ["8.8.8.8:53" "8.8.4.4:53"];
     };
 
-    package = mkOption {
-      default = pkgs.skydns;
-      defaultText = literalExpression "pkgs.skydns";
-      type = types.package;
-      description = lib.mdDoc "Skydns package to use.";
-    };
+    package = mkPackageOption pkgs "skydns" { };
 
     extraConfig = mkOption {
       default = {};
diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index c7aec7d9489f9..4ecf411c74967 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -165,12 +165,7 @@ in
         example = "no-reply@yourdomain.com";
         description = lib.mdDoc "Email contact for owner";
       };
-      package = mkOption {
-        type = types.package;
-        default = pkgs.smokeping;
-        defaultText = literalExpression "pkgs.smokeping";
-        description = lib.mdDoc "Specify a custom smokeping package";
-      };
+      package = mkPackageOption pkgs "smokeping" { };
       host = mkOption {
         type = types.nullOr types.str;
         default = "localhost";
diff --git a/nixos/modules/services/networking/snowflake-proxy.nix b/nixos/modules/services/networking/snowflake-proxy.nix
index ca015ed9d44bc..19b68f1e20ba6 100644
--- a/nixos/modules/services/networking/snowflake-proxy.nix
+++ b/nixos/modules/services/networking/snowflake-proxy.nix
@@ -8,7 +8,7 @@ in
 {
   options = {
     services.snowflake-proxy = {
-      enable = mkEnableOption (lib.mdDoc "System to defeat internet censorship");
+      enable = mkEnableOption (lib.mdDoc "snowflake-proxy, a system to defeat internet censorship");
 
       broker = mkOption {
         description = lib.mdDoc "Broker URL (default \"https://snowflake-broker.torproject.net/\")";
diff --git a/nixos/modules/services/networking/softether.nix b/nixos/modules/services/networking/softether.nix
index c8e888eafcc24..234832ea0c0f8 100644
--- a/nixos/modules/services/networking/softether.nix
+++ b/nixos/modules/services/networking/softether.nix
@@ -18,14 +18,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "SoftEther VPN services");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.softether;
-        defaultText = literalExpression "pkgs.softether";
-        description = lib.mdDoc ''
-          softether derivation to use.
-        '';
-      };
+      package = mkPackageOption pkgs "softether" { };
 
       vpnserver.enable = mkEnableOption (lib.mdDoc "SoftEther VPN Server");
 
diff --git a/nixos/modules/services/networking/soju.nix b/nixos/modules/services/networking/soju.nix
index 7f0ac3e3b8e69..d69ec08ca13a0 100644
--- a/nixos/modules/services/networking/soju.nix
+++ b/nixos/modules/services/networking/soju.nix
@@ -110,6 +110,7 @@ in
     systemd.services.soju = {
       description = "soju IRC bouncer";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       serviceConfig = {
         DynamicUser = true;
diff --git a/nixos/modules/services/networking/spacecookie.nix b/nixos/modules/services/networking/spacecookie.nix
index b2956edfcb7f3..745c942ba60b7 100644
--- a/nixos/modules/services/networking/spacecookie.nix
+++ b/nixos/modules/services/networking/spacecookie.nix
@@ -27,15 +27,8 @@ in {
 
       enable = mkEnableOption (lib.mdDoc "spacecookie");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.spacecookie;
-        defaultText = literalExpression "pkgs.spacecookie";
-        example = literalExpression "pkgs.haskellPackages.spacecookie";
-        description = lib.mdDoc ''
-          The spacecookie derivation to use. This can be used to
-          override the used package or to use another version.
-        '';
+      package = mkPackageOption pkgs "spacecookie" {
+        example = "haskellPackages.spacecookie";
       };
 
       openFirewall = mkOption {
diff --git a/nixos/modules/services/networking/spiped.nix b/nixos/modules/services/networking/spiped.nix
index 3e01ace54ad17..547317dbcbe2a 100644
--- a/nixos/modules/services/networking/spiped.nix
+++ b/nixos/modules/services/networking/spiped.nix
@@ -197,8 +197,9 @@ in
       script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`";
     };
 
-    system.activationScripts.spiped = optionalString (cfg.config != {})
-      "mkdir -p /var/lib/spiped";
+    systemd.tmpfiles.rules = lib.mkIf (cfg.config != { }) [
+      "d /var/lib/spiped -"
+    ];
 
     # Setup spiped config files
     environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec"
diff --git a/nixos/modules/services/networking/squid.nix b/nixos/modules/services/networking/squid.nix
index 914cd7f320c9b..68f4dc3d6dc12 100644
--- a/nixos/modules/services/networking/squid.nix
+++ b/nixos/modules/services/networking/squid.nix
@@ -111,12 +111,7 @@ in
         description = lib.mdDoc "Whether to run squid web proxy.";
       };
 
-      package = mkOption {
-        default = pkgs.squid;
-        defaultText = literalExpression "pkgs.squid";
-        type = types.package;
-        description = lib.mdDoc "Squid package to use.";
-      };
+      package = mkPackageOption pkgs "squid" { };
 
       proxyAddress = mkOption {
         type = types.nullOr types.str;
@@ -176,7 +171,7 @@ in
       serviceConfig = {
         PIDFile="/run/squid.pid";
         ExecStart  = "${cfg.package}/bin/squid --foreground -YCs -f ${squidConfig}";
-        ExecReload="kill -HUP $MAINPID";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         KillMode="mixed";
         NotifyAccess="all";
       };
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index e75239e059d3b..aca8343b7d597 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -12,29 +12,48 @@ let
     then cfgc.package
     else pkgs.buildPackages.openssh;
 
-  # reports boolean as yes / no
-  mkValueStringSshd = with lib; v:
-        if isInt           v then toString v
-        else if isString   v then v
-        else if true  ==   v then "yes"
-        else if false ==   v then "no"
-        else if isList     v then concatStringsSep "," v
-        else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
-
   # dont use the "=" operator
-  settingsFormat = (pkgs.formats.keyValue {
-      mkKeyValue = lib.generators.mkKeyValueDefault {
-      mkValueString = mkValueStringSshd;
-    } " ";});
+  settingsFormat =
+    let
+      # reports boolean as yes / no
+      mkValueString = with lib; v:
+            if isInt           v then toString v
+            else if isString   v then v
+            else if true  ==   v then "yes"
+            else if false ==   v then "no"
+            else throw "unsupported type ${builtins.typeOf v}: ${(lib.generators.toPretty {}) v}";
+
+      base = pkgs.formats.keyValue {
+        mkKeyValue = lib.generators.mkKeyValueDefault { inherit mkValueString; } " ";
+      };
+      # OpenSSH is very inconsistent with options that can take multiple values.
+      # For some of them, they can simply appear multiple times and are appended, for others the
+      # values must be separated by whitespace or even commas.
+      # Consult either sshd_config(5) or, as last resort, the OpehSSH source for parsing
+      # the options at servconf.c:process_server_config_line_depth() to determine the right "mode"
+      # for each. But fortunaly this fact is documented for most of them in the manpage.
+      commaSeparated = [ "Ciphers" "KexAlgorithms" "Macs" ];
+      spaceSeparated = [ "AuthorizedKeysFile" "AllowGroups" "AllowUsers" "DenyGroups" "DenyUsers" ];
+    in {
+      inherit (base) type;
+      generate = name: value:
+        let transformedValue = mapAttrs (key: val:
+          if isList val then
+            if elem key commaSeparated then concatStringsSep "," val
+            else if elem key spaceSeparated then concatStringsSep " " val
+            else throw "list value for unknown key ${key}: ${(lib.generators.toPretty {}) val}"
+          else
+            val
+          ) value;
+        in
+          base.generate name transformedValue;
+    };
 
-  configFile = settingsFormat.generate "config" cfg.settings;
-  sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } ''
+  configFile = settingsFormat.generate "sshd.conf-settings" (filterAttrs (n: v: v != null) cfg.settings);
+  sshconf = pkgs.runCommand "sshd.conf-final" { } ''
     cat ${configFile} - >$out <<EOL
     ${cfg.extraConfig}
     EOL
-
-    ssh-keygen -q -f mock-hostkey -N ""
-    sshd -t -f $out -h mock-hostkey
   '';
 
   cfg  = config.services.openssh;
@@ -77,6 +96,19 @@ let
       };
     };
 
+    options.openssh.authorizedPrincipals = mkOption {
+      type = with types; listOf types.singleLineStr;
+      default = [];
+      description = mdDoc ''
+        A list of verbatim principal names that should be added to the user's
+        authorized principals.
+      '';
+      example = [
+        "example@host"
+        "foo@bar"
+      ];
+    };
+
   };
 
   authKeysFiles = let
@@ -92,6 +124,16 @@ let
     ));
   in listToAttrs (map mkAuthKeyFile usersWithKeys);
 
+  authPrincipalsFiles = let
+    mkAuthPrincipalsFile = u: nameValuePair "ssh/authorized_principals.d/${u.name}" {
+      mode = "0444";
+      text = concatStringsSep "\n" u.openssh.authorizedPrincipals;
+    };
+    usersWithPrincipals = attrValues (flip filterAttrs config.users.users (n: u:
+      length u.openssh.authorizedPrincipals != 0
+    ));
+  in listToAttrs (map mkAuthPrincipalsFile usersWithPrincipals);
+
 in
 
 {
@@ -288,6 +330,14 @@ in
         type = types.submodule ({name, ...}: {
           freeformType = settingsFormat.type;
           options = {
+            AuthorizedPrincipalsFile = mkOption {
+              type = types.str;
+              default = "none"; # upstream default
+              description = lib.mdDoc ''
+                Specifies a file that lists principal names that are accepted for certificate authentication. The default
+                is `"none"`, i.e. not to use	a principals file.
+              '';
+            };
             LogLevel = mkOption {
               type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ];
               default = "INFO"; # upstream default
@@ -403,6 +453,42 @@ in
                 <https://infosec.mozilla.org/guidelines/openssh#modern-openssh-67>
               '';
             };
+            AllowUsers = mkOption {
+              type = with types; nullOr (listOf str);
+              default = null;
+              description = lib.mdDoc ''
+                If specified, login is allowed only for the listed users.
+                See {manpage}`sshd_config(5)` for details.
+              '';
+            };
+            DenyUsers = mkOption {
+              type = with types; nullOr (listOf str);
+              default = null;
+              description = lib.mdDoc ''
+                If specified, login is denied for all listed users. Takes
+                precedence over [](#opt-services.openssh.settings.AllowUsers).
+                See {manpage}`sshd_config(5)` for details.
+              '';
+            };
+            AllowGroups = mkOption {
+              type = with types; nullOr (listOf str);
+              default = null;
+              description = lib.mdDoc ''
+                If specified, login is allowed only for users part of the
+                listed groups.
+                See {manpage}`sshd_config(5)` for details.
+              '';
+            };
+            DenyGroups = mkOption {
+              type = with types; nullOr (listOf str);
+              default = null;
+              description = lib.mdDoc ''
+                If specified, login is denied for all users part of the listed
+                groups. Takes precedence over
+                [](#opt-services.openssh.settings.AllowGroups). See
+                {manpage}`sshd_config(5)` for details.
+              '';
+            };
           };
         });
       };
@@ -447,7 +533,7 @@ in
     services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli";
     services.openssh.sftpServerExecutable = mkDefault "${cfgc.package}/libexec/sftp-server";
 
-    environment.etc = authKeysFiles //
+    environment.etc = authKeysFiles // authPrincipalsFiles //
       { "ssh/moduli".source = cfg.moduliFile;
         "ssh/sshd_config".source = sshconf;
       };
@@ -514,7 +600,11 @@ in
           { description = "SSH Socket";
             wantedBy = [ "sockets.target" ];
             socketConfig.ListenStream = if cfg.listenAddresses != [] then
-              map (l: "${l.addr}:${toString (if l.port != null then l.port else 22)}") cfg.listenAddresses
+              concatMap
+                ({ addr, port }:
+                  if port != null then [ "${addr}:${toString port}" ]
+                  else map (p: "${addr}:${toString p}") cfg.ports)
+                cfg.listenAddresses
             else
               cfg.ports;
             socketConfig.Accept = true;
@@ -544,6 +634,8 @@ in
     services.openssh.authorizedKeysFiles =
       [ "%h/.ssh/authorized_keys" "/etc/ssh/authorized_keys.d/%u" ];
 
+    services.openssh.settings.AuthorizedPrincipalsFile = mkIf (authPrincipalsFiles != {}) "/etc/ssh/authorized_principals.d/%u";
+
     services.openssh.extraConfig = mkOrder 0
       ''
         UsePAM yes
@@ -577,6 +669,25 @@ in
         '')}
       '';
 
+    system.checks = [
+      (pkgs.runCommand "check-sshd-config"
+        {
+          nativeBuildInputs = [ validationPackage ];
+        } ''
+        ${concatMapStringsSep "\n"
+          (lport: "sshd -G -T -C lport=${toString lport} -f ${sshconf} > /dev/null")
+          cfg.ports}
+        ${concatMapStringsSep "\n"
+          (la:
+            concatMapStringsSep "\n"
+              (port: "sshd -G -T -C ${escapeShellArg "laddr=${la.addr},lport=${toString port}"} -f ${sshconf} > /dev/null")
+              (if la.port != null then [ la.port ] else cfg.ports)
+          )
+          cfg.listenAddresses}
+        touch $out
+      '')
+    ];
+
     assertions = [{ assertion = if cfg.settings.X11Forwarding then cfgc.setXAuthLocation else true;
                     message = "cannot enable X11 forwarding without setting xauth location";}
                   (let
diff --git a/nixos/modules/services/networking/sslh.nix b/nixos/modules/services/networking/sslh.nix
index daf2f2f3668ee..dd29db510020a 100644
--- a/nixos/modules/services/networking/sslh.nix
+++ b/nixos/modules/services/networking/sslh.nix
@@ -5,81 +5,131 @@ with lib;
 let
   cfg = config.services.sslh;
   user = "sslh";
-  configFile = pkgs.writeText "sslh.conf" ''
-    verbose: ${boolToString cfg.verbose};
-    foreground: true;
-    inetd: false;
-    numeric: false;
-    transparent: ${boolToString cfg.transparent};
-    timeout: "${toString cfg.timeout}";
-
-    listen:
-    (
-      ${
-        concatMapStringsSep ",\n"
-        (addr: ''{ host: "${addr}"; port: "${toString cfg.port}"; }'')
-        cfg.listenAddresses
-      }
-    );
-
-    ${cfg.appendConfig}
-  '';
-  defaultAppendConfig = ''
-    protocols:
-    (
-      { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
-      { name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; },
-      { name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; },
-      { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
-      { name: "tls"; host: "localhost"; port: "443"; probe: "builtin"; },
-      { name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; }
-    );
-  '';
+
+  configFormat = pkgs.formats.libconfig {};
+  configFile = configFormat.generate "sslh.conf" cfg.settings;
 in
+
 {
   imports = [
     (mkRenamedOptionModule [ "services" "sslh" "listenAddress" ] [ "services" "sslh" "listenAddresses" ])
+    (mkRenamedOptionModule [ "services" "sslh" "timeout" ] [ "services" "sslh" "settings" "timeout" ])
+    (mkRenamedOptionModule [ "services" "sslh" "transparent" ] [ "services" "sslh" "settings" "transparent" ])
+    (mkRemovedOptionModule [ "services" "sslh" "appendConfig" ] "Use services.sslh.settings instead")
+    (mkChangedOptionModule [ "services" "sslh" "verbose" ] [ "services" "sslh" "settings" "verbose-connections" ]
+      (config: if config.services.sslh.verbose then 1 else 0))
   ];
 
-  options = {
-    services.sslh = {
-      enable = mkEnableOption (lib.mdDoc "sslh");
+  meta.buildDocsInSandbox = false;
 
-      verbose = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Verbose logs.";
-      };
+  options.services.sslh = {
+    enable = mkEnableOption (lib.mdDoc "sslh, protocol demultiplexer");
 
-      timeout = mkOption {
-        type = types.int;
-        default = 2;
-        description = lib.mdDoc "Timeout in seconds.";
-      };
+    method = mkOption {
+      type = types.enum [ "fork" "select" "ev" ];
+      default = "fork";
+      description = lib.mdDoc ''
+        The method to use for handling connections:
 
-      transparent = mkOption {
-        type = types.bool;
-        default = false;
-        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";
-      };
+          - `fork` forks a new process for each incoming connection. It is
+          well-tested and very reliable, but incurs the overhead of many
+          processes.
 
-      listenAddresses = mkOption {
-        type = types.coercedTo types.str singleton (types.listOf types.str);
-        default = [ "0.0.0.0" "[::]" ];
-        description = lib.mdDoc "Listening addresses or hostnames.";
-      };
+          - `select` uses only one thread, which monitors all connections at once.
+          It has lower overhead per connection, but if it stops, you'll lose all
+          connections.
 
-      port = mkOption {
-        type = types.port;
-        default = 443;
-        description = lib.mdDoc "Listening port.";
-      };
+          - `ev` is implemented using libev, it's similar to `select` but
+            scales better to a large number of connections.
+      '';
+    };
+
+    listenAddresses = mkOption {
+      type = with types; coercedTo str singleton (listOf str);
+      default = [ "0.0.0.0" "[::]" ];
+      description = lib.mdDoc "Listening addresses or hostnames.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 443;
+      description = lib.mdDoc "Listening port.";
+    };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = configFormat.type;
+
+        options.timeout = mkOption {
+          type = types.ints.unsigned;
+          default = 2;
+          description = lib.mdDoc "Timeout in seconds.";
+        };
+
+        options.transparent = mkOption {
+          type = types.bool;
+          default = false;
+          description = lib.mdDoc ''
+            Whether the services behind sslh (Apache, sshd and so on) will see the
+            external IP and ports as if the external world connected directly to
+            them.
+          '';
+        };
+
+        options.verbose-connections = mkOption {
+          type = types.ints.between 0 4;
+          default = 0;
+          description = lib.mdDoc ''
+            Where to log connections information. Possible values are:
+
+             0. don't log anything
+             1. write log to stdout
+             2. write log to syslog
+             3. write log to both stdout and syslog
+             4. write to a log file ({option}`sslh.settings.logfile`)
+          '';
+        };
+
+        options.numeric = mkOption {
+          type = types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to disable reverse DNS lookups, thus keeping IP
+            address literals in the log.
+          '';
+        };
+
+        options.protocols = mkOption {
+          type = types.listOf configFormat.type;
+          default = [
+            { name = "ssh";     host = "localhost"; port =  "22"; service= "ssh"; }
+            { name = "openvpn"; host = "localhost"; port = "1194"; }
+            { name = "xmpp";    host = "localhost"; port = "5222"; }
+            { name = "http";    host = "localhost"; port =   "80"; }
+            { name = "tls";     host = "localhost"; port =  "443"; }
+            { name = "anyprot"; host = "localhost"; port =  "443"; }
+          ];
+          description = lib.mdDoc ''
+            List of protocols sslh will probe for and redirect.
+            Each protocol entry consists of:
+
+              - `name`: name of the probe.
+
+              - `service`: libwrap service name (see {manpage}`hosts_access(5)`),
 
-      appendConfig = mkOption {
-        type = types.str;
-        default = defaultAppendConfig;
-        description = lib.mdDoc "Verbatim configuration file.";
+              - `host`, `port`: where to connect when this probe succeeds,
+
+              - `log_level`: to log incoming connections,
+
+              - `transparent`: proxy this protocol transparently,
+
+              - etc.
+
+            See the documentation for all options, including probe-specific ones.
+          '';
+        };
       };
+      description = lib.mdDoc "sslh configuration. See {manpage}`sslh(8)` for available settings.";
     };
   };
 
@@ -96,20 +146,29 @@ in
           PermissionsStartOnly = true;
           Restart              = "always";
           RestartSec           = "1s";
-          ExecStart            = "${pkgs.sslh}/bin/sslh -F${configFile}";
+          ExecStart            = "${pkgs.sslh}/bin/sslh-${cfg.method} -F${configFile}";
           KillMode             = "process";
-          AmbientCapabilities  = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID";
+          AmbientCapabilities  = ["CAP_NET_BIND_SERVICE" "CAP_NET_ADMIN" "CAP_SETGID" "CAP_SETUID"];
           PrivateTmp           = true;
           PrivateDevices       = true;
           ProtectSystem        = "full";
           ProtectHome          = true;
         };
       };
+
+      services.sslh.settings = {
+        # Settings defined here are not supposed to be changed: doing so will
+        # break the module, as such you need `lib.mkForce` to override them.
+        foreground = true;
+        inetd = false;
+        listen = map (addr: { host = addr; port = toString cfg.port; }) cfg.listenAddresses;
+      };
+
     })
 
     # code from https://github.com/yrutschle/sslh#transparent-proxy-support
     # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module
-    (mkIf (cfg.enable && cfg.transparent) {
+    (mkIf (cfg.enable && cfg.settings.transparent) {
       # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
       boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1;
       boot.kernel.sysctl."net.ipv4.conf.all.route_localnet"     = 1;
diff --git a/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixos/modules/services/networking/strongswan-swanctl/module.nix
index c51e8ad9f5fc9..a988509239558 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/module.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/module.nix
@@ -10,14 +10,7 @@ in  {
   options.services.strongswan-swanctl = {
     enable = mkEnableOption (lib.mdDoc "strongswan-swanctl service");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.strongswan;
-      defaultText = literalExpression "pkgs.strongswan";
-      description = lib.mdDoc ''
-        The strongswan derivation to use.
-      '';
-    };
+    package = mkPackageOption pkgs "strongswan" { };
 
     strongswan.extraConfig = mkOption {
       type = types.str;
@@ -43,25 +36,26 @@ in  {
 
     # The swanctl command complains when the following directories don't exist:
     # See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctldirectory
-    system.activationScripts.strongswan-swanctl-etc = stringAfter ["etc"] ''
-      mkdir -p '/etc/swanctl/x509'     # Trusted X.509 end entity certificates
-      mkdir -p '/etc/swanctl/x509ca'   # Trusted X.509 Certificate Authority certificates
-      mkdir -p '/etc/swanctl/x509ocsp'
-      mkdir -p '/etc/swanctl/x509aa'   # Trusted X.509 Attribute Authority certificates
-      mkdir -p '/etc/swanctl/x509ac'   # Attribute Certificates
-      mkdir -p '/etc/swanctl/x509crl'  # Certificate Revocation Lists
-      mkdir -p '/etc/swanctl/pubkey'   # Raw public keys
-      mkdir -p '/etc/swanctl/private'  # Private keys in any format
-      mkdir -p '/etc/swanctl/rsa'      # PKCS#1 encoded RSA private keys
-      mkdir -p '/etc/swanctl/ecdsa'    # Plain ECDSA private keys
-      mkdir -p '/etc/swanctl/bliss'
-      mkdir -p '/etc/swanctl/pkcs8'    # PKCS#8 encoded private keys of any type
-      mkdir -p '/etc/swanctl/pkcs12'   # PKCS#12 containers
-    '';
+    systemd.tmpfiles.rules = [
+      "d /etc/swanctl/x509 -"     # Trusted X.509 end entity certificates
+      "d /etc/swanctl/x509ca -"   # Trusted X.509 Certificate Authority certificates
+      "d /etc/swanctl/x509ocsp -"
+      "d /etc/swanctl/x509aa -"   # Trusted X.509 Attribute Authority certificates
+      "d /etc/swanctl/x509ac -"   # Attribute Certificates
+      "d /etc/swanctl/x509crl -"  # Certificate Revocation Lists
+      "d /etc/swanctl/pubkey -"   # Raw public keys
+      "d /etc/swanctl/private -"  # Private keys in any format
+      "d /etc/swanctl/rsa -"      # PKCS#1 encoded RSA private keys
+      "d /etc/swanctl/ecdsa -"    # Plain ECDSA private keys
+      "d /etc/swanctl/bliss -"
+      "d /etc/swanctl/pkcs8 -"    # PKCS#8 encoded private keys of any type
+      "d /etc/swanctl/pkcs12 -"   # PKCS#12 containers
+    ];
 
     systemd.services.strongswan-swanctl = {
       description = "strongSwan IPsec IKEv1/IKEv2 daemon using swanctl";
       wantedBy = [ "multi-user.target" ];
+      wants    = [ "network-online.target" ];
       after    = [ "network-online.target" ];
       path     = with pkgs; [ kmod iproute2 iptables util-linux ];
       environment = {
diff --git a/nixos/modules/services/networking/strongswan.nix b/nixos/modules/services/networking/strongswan.nix
index e58526814d1ad..dcf04d2a1917c 100644
--- a/nixos/modules/services/networking/strongswan.nix
+++ b/nixos/modules/services/networking/strongswan.nix
@@ -153,6 +153,7 @@ in
       description = "strongSwan IPSec Service";
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ kmod iproute2 iptables util-linux ]; # XXX Linux
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       environment = {
         STRONGSWAN_CONF = strongswanConf { inherit setup connections ca secretsFile managePlugins enabledPlugins; };
diff --git a/nixos/modules/services/networking/syncplay.nix b/nixos/modules/services/networking/syncplay.nix
index 0a66d93bf153a..151259b6d4ad2 100644
--- a/nixos/modules/services/networking/syncplay.nix
+++ b/nixos/modules/services/networking/syncplay.nix
@@ -107,6 +107,7 @@ in
     systemd.services.syncplay = {
       description = "Syncplay Service";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
 
       serviceConfig = {
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 346b50700c798..e0425792431e6 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -10,6 +10,21 @@ let
   settingsFormat = pkgs.formats.json { };
   cleanedConfig = converge (filterAttrsRecursive (_: v: v != null && v != {})) cfg.settings;
 
+  isUnixGui = (builtins.substring 0 1 cfg.guiAddress) == "/";
+
+  # Syncthing supports serving the GUI over Unix sockets. If that happens, the
+  # API is served over the Unix socket as well.  This function returns the correct
+  # curl arguments for the address portion of the curl command for both network
+  # and Unix socket addresses.
+  curlAddressArgs = path: if isUnixGui
+    # if cfg.guiAddress is a unix socket, tell curl explicitly about it
+    # note that the dot in front of `${path}` is the hostname, which is
+    # required.
+    then "--unix-socket ${cfg.guiAddress} http://.${path}"
+    # no adjustements are needed if cfg.guiAddress is a network address
+    else "${cfg.guiAddress}${path}"
+    ;
+
   devices = mapAttrsToList (_: device: device // {
     deviceID = device.id;
   }) cfg.settings.devices;
@@ -36,17 +51,15 @@ let
     # 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
-        ! ${pkgs.libxml2}/bin/xmllint \
-            --xpath 'string(configuration/gui/apikey)' \
-            ${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() {
+        # get the api key by parsing the config.xml
+        while
+            ! ${pkgs.libxml2}/bin/xmllint \
+                --xpath 'string(configuration/gui/apikey)' \
+                ${cfg.configDir}/config.xml \
+                >"$RUNTIME_DIRECTORY/api_key"
+        do sleep 1; done
+        (printf "X-API-Key: "; cat "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
         ${pkgs.curl}/bin/curl -sSLk -H "@$RUNTIME_DIRECTORY/headers" \
             --retry 1000 --retry-delay 1 --retry-all-errors \
             "$@"
@@ -64,14 +77,14 @@ let
       GET_IdAttrName = "deviceID";
       override = cfg.overrideDevices;
       conf = devices;
-      baseAddress = "${cfg.guiAddress}/rest/config/devices";
+      baseAddress = curlAddressArgs "/rest/config/devices";
     };
     dirs = {
       new_conf_IDs = map (v: v.id) folders;
       GET_IdAttrName = "id";
       override = cfg.overrideFolders;
       conf = folders;
-      baseAddress = "${cfg.guiAddress}/rest/config/folders";
+      baseAddress = curlAddressArgs "/rest/config/folders";
     };
   } [
     # Now for each of these attributes, write the curl commands that are
@@ -100,13 +113,13 @@ let
       the Nix configured list of IDs
       */
       + lib.optionalString s.override ''
-        old_conf_${conf_type}_ids="$(curl -X GET ${s.baseAddress} | ${jq} --raw-output '.[].${s.GET_IdAttrName}')"
-        for id in ''${old_conf_${conf_type}_ids}; do
-          if echo ${lib.concatStringsSep " " s.new_conf_IDs} | grep -q $id; then
-            continue
-          else
-            curl -X DELETE ${s.baseAddress}/$id
-          fi
+        stale_${conf_type}_ids="$(curl -X GET ${s.baseAddress} | ${jq} \
+          --argjson new_ids ${lib.escapeShellArg (builtins.toJSON s.new_conf_IDs)} \
+          --raw-output \
+          '[.[].${s.GET_IdAttrName}] - $new_ids | .[]'
+        )"
+        for id in ''${stale_${conf_type}_ids}; do
+          curl -X DELETE ${s.baseAddress}/$id
         done
       ''
     ))
@@ -119,15 +132,14 @@ let
     builtins.attrNames
     (lib.subtractLists ["folders" "devices"])
     (map (subOption: ''
-      curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} \
-        ${cfg.guiAddress}/rest/config/${subOption}
+      curl -X PUT -d ${lib.escapeShellArg (builtins.toJSON cleanedConfig.${subOption})} ${curlAddressArgs "/rest/config/${subOption}"}
     ''))
     (lib.concatStringsSep "\n")
   ]) + ''
     # restart Syncthing if required
-    if curl ${cfg.guiAddress}/rest/config/restart-required |
+    if curl ${curlAddressArgs "/rest/config/restart-required"} |
        ${jq} -e .requiresRestart > /dev/null; then
-        curl -X POST ${cfg.guiAddress}/rest/system/restart
+        curl -X POST ${curlAddressArgs "/rest/system/restart"}
     fi
   '');
 in {
@@ -547,6 +559,15 @@ in {
         '';
       };
 
+      databaseDir = mkOption {
+        type = types.path;
+        description = lib.mdDoc ''
+          The directory containing the database and logs.
+        '';
+        default = cfg.configDir;
+        defaultText = literalExpression "config.${opt.configDir}";
+      };
+
       extraFlags = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -571,14 +592,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.syncthing;
-        defaultText = literalExpression "pkgs.syncthing";
-        description = lib.mdDoc ''
-          The Syncthing package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "syncthing" { };
     };
   };
 
@@ -653,8 +667,10 @@ in {
           ExecStart = ''
             ${cfg.package}/bin/syncthing \
               -no-browser \
-              -gui-address=${cfg.guiAddress} \
-              -home=${cfg.configDir} ${escapeShellArgs cfg.extraFlags}
+              -gui-address=${if isUnixGui then "unix://" else ""}${cfg.guiAddress} \
+              -config=${cfg.configDir} \
+              -data=${cfg.databaseDir} \
+              ${escapeShellArgs cfg.extraFlags}
           '';
           MemoryDenyWriteExecute = true;
           NoNewPrivileges = true;
diff --git a/nixos/modules/services/networking/tailscale.nix b/nixos/modules/services/networking/tailscale.nix
index 8b35cc8d66697..1070e4e252967 100644
--- a/nixos/modules/services/networking/tailscale.nix
+++ b/nixos/modules/services/networking/tailscale.nix
@@ -29,7 +29,13 @@ in {
       description = lib.mdDoc "Username or user ID of the user allowed to to fetch Tailscale TLS certificates for the node.";
     };
 
-    package = lib.mkPackageOptionMD pkgs "tailscale" {};
+    package = lib.mkPackageOption pkgs "tailscale" {};
+
+    openFirewall = mkOption {
+      default = false;
+      type = types.bool;
+      description = lib.mdDoc "Whether to open the firewall for the specified port.";
+    };
 
     useRoutingFeatures = mkOption {
       type = types.enum [ "none" "client" "server" "both" ];
@@ -94,8 +100,8 @@ in {
     };
 
     systemd.services.tailscaled-autoconnect = mkIf (cfg.authKeyFile != null) {
-      after = ["tailscale.service"];
-      wants = ["tailscale.service"];
+      after = ["tailscaled.service"];
+      wants = ["tailscaled.service"];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         Type = "oneshot";
@@ -113,6 +119,8 @@ in {
       "net.ipv6.conf.all.forwarding" = mkOverride 97 true;
     };
 
+    networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
     networking.firewall.checkReversePath = mkIf (cfg.useRoutingFeatures == "client" || cfg.useRoutingFeatures == "both") "loose";
 
     networking.dhcpcd.denyInterfaces = [ cfg.interfaceName ];
diff --git a/nixos/modules/services/networking/tayga.nix b/nixos/modules/services/networking/tayga.nix
index 299ae2777f7c0..63423bf029222 100644
--- a/nixos/modules/services/networking/tayga.nix
+++ b/nixos/modules/services/networking/tayga.nix
@@ -64,12 +64,7 @@ in
     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.";
-      };
+      package = mkPackageOption pkgs "tayga" { };
 
       ipv4 = mkOption {
         type = types.submodule (versionOpts 4);
diff --git a/nixos/modules/services/networking/teamspeak3.nix b/nixos/modules/services/networking/teamspeak3.nix
index f09ef1a959ed4..ff41539a6d9b7 100644
--- a/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixos/modules/services/networking/teamspeak3.nix
@@ -50,7 +50,7 @@ in
       };
 
       defaultVoicePort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 9987;
         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.
@@ -67,7 +67,7 @@ in
       };
 
       fileTransferPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 30033;
         description = lib.mdDoc ''
           TCP port opened for file transfers.
@@ -84,10 +84,26 @@ in
       };
 
       queryPort = mkOption {
-        type = types.int;
+        type = types.port;
         default = 10011;
         description = lib.mdDoc ''
-          TCP port opened for ServerQuery connections.
+          TCP port opened for ServerQuery connections using the raw telnet protocol.
+        '';
+      };
+
+      querySshPort = mkOption {
+        type = types.port;
+        default = 10022;
+        description = lib.mdDoc ''
+          TCP port opened for ServerQuery connections using the SSH protocol.
+        '';
+      };
+
+      queryHttpPort = mkOption {
+        type = types.port;
+        default = 10080;
+        description = lib.mdDoc ''
+          TCP port opened for ServerQuery connections using the HTTP protocol.
         '';
       };
 
@@ -128,7 +144,9 @@ in
     ];
 
     networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.fileTransferPort ] ++ optionals (cfg.openFirewallServerQuery) [ cfg.queryPort (cfg.queryPort + 11) ];
+      allowedTCPPorts = [ cfg.fileTransferPort ] ++ (map (port:
+        mkIf cfg.openFirewallServerQuery port
+      ) [cfg.queryPort cfg.querySshPort cfg.queryHttpPort]);
       # subsequent vServers will use the incremented voice port, let's just open the next 10
       allowedUDPPortRanges = [ { from = cfg.defaultVoicePort; to = cfg.defaultVoicePort + 10; } ];
     };
@@ -141,13 +159,19 @@ in
       serviceConfig = {
         ExecStart = ''
           ${ts3}/bin/ts3server \
-            dbsqlpath=${ts3}/lib/teamspeak/sql/ logpath=${cfg.logPath} \
-            ${optionalString (cfg.voiceIP != null) "voice_ip=${cfg.voiceIP}"} \
+            dbsqlpath=${ts3}/lib/teamspeak/sql/ \
+            logpath=${cfg.logPath} \
+            license_accepted=1 \
             default_voice_port=${toString cfg.defaultVoicePort} \
-            ${optionalString (cfg.fileTransferIP != null) "filetransfer_ip=${cfg.fileTransferIP}"} \
             filetransfer_port=${toString cfg.fileTransferPort} \
+            query_port=${toString cfg.queryPort} \
+            query_ssh_port=${toString cfg.querySshPort} \
+            query_http_port=${toString cfg.queryHttpPort} \
+            ${optionalString (cfg.voiceIP != null) "voice_ip=${cfg.voiceIP}"} \
+            ${optionalString (cfg.fileTransferIP != null) "filetransfer_ip=${cfg.fileTransferIP}"} \
             ${optionalString (cfg.queryIP != null) "query_ip=${cfg.queryIP}"} \
-            query_port=${toString cfg.queryPort} license_accepted=1
+            ${optionalString (cfg.queryIP != null) "query_ssh_ip=${cfg.queryIP}"} \
+            ${optionalString (cfg.queryIP != null) "query_http_ip=${cfg.queryIP}"} \
         '';
         WorkingDirectory = cfg.dataDir;
         User = user;
diff --git a/nixos/modules/services/networking/tedicross.nix b/nixos/modules/services/networking/tedicross.nix
deleted file mode 100644
index cee7e11f4fb15..0000000000000
--- a/nixos/modules/services/networking/tedicross.nix
+++ /dev/null
@@ -1,100 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  dataDir = "/var/lib/tedicross";
-  cfg = config.services.tedicross;
-  configJSON = pkgs.writeText "tedicross-settings.json" (builtins.toJSON cfg.config);
-  configYAML = pkgs.runCommand "tedicross-settings.yaml" { preferLocalBuild = true; } ''
-    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
-  '';
-
-in {
-  options = {
-    services.tedicross = {
-      enable = mkEnableOption (lib.mdDoc "the TediCross Telegram-Discord bridge service");
-
-      config = mkOption {
-        type = types.attrs;
-        # from https://github.com/TediCross/TediCross/blob/master/example.settings.yaml
-        example = literalExpression ''
-          {
-            telegram = {
-              useFirstNameInsteadOfUsername = false;
-              colonAfterSenderName = false;
-              skipOldMessages = true;
-              sendEmojiWithStickers = true;
-            };
-            discord = {
-              useNickname = false;
-              skipOldMessages = true;
-              displayTelegramReplies = "embed";
-              replyLength = 100;
-            };
-            bridges = [
-              {
-                name = "Default bridge";
-                direction = "both";
-                telegram = {
-                  chatId = -123456789;
-                  relayJoinMessages = true;
-                  relayLeaveMessages = true;
-                  sendUsernames = true;
-                  ignoreCommands = true;
-                };
-                discord = {
-                  serverId = "DISCORD_SERVER_ID";
-                  channelId = "DISCORD_CHANNEL_ID";
-                  relayJoinMessages = true;
-                  relayLeaveMessages = true;
-                  sendUsernames = true;
-                  crossDeleteOnTelegram = true;
-                };
-              }
-            ];
-
-            debug = false;
-          }
-        '';
-        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.
-        '';
-      };
-
-      environmentFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = lib.mdDoc ''
-          File containing environment variables to be passed to the TediCross service,
-          in which secret tokens can be specified securely using the
-          `TELEGRAM_BOT_TOKEN` and `DISCORD_BOT_TOKEN`
-          keys.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    # from https://github.com/TediCross/TediCross/blob/master/guides/autostart/Linux.md
-    systemd.services.tedicross = {
-      description = "TediCross Telegram-Discord bridge service";
-      wantedBy = [ "multi-user.target" ];
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-      serviceConfig = {
-        Type = "simple";
-        ExecStart = "${pkgs.nodePackages.tedicross}/bin/tedicross --config='${configYAML}' --data-dir='${dataDir}'";
-        Restart = "always";
-        DynamicUser = true;
-        StateDirectory = baseNameOf dataDir;
-        EnvironmentFile = cfg.environmentFile;
-      };
-    };
-  };
-
-  meta.maintainers = with maintainers; [ pacien ];
-}
-
diff --git a/nixos/modules/services/networking/teleport.nix b/nixos/modules/services/networking/teleport.nix
index 399af711c0e12..add6b47315b1d 100644
--- a/nixos/modules/services/networking/teleport.nix
+++ b/nixos/modules/services/networking/teleport.nix
@@ -11,12 +11,8 @@ in
     services.teleport = with lib.types; {
       enable = mkEnableOption (lib.mdDoc "the Teleport service");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.teleport;
-        defaultText = lib.literalMD "pkgs.teleport";
-        example = lib.literalMD "pkgs.teleport_11";
-        description = lib.mdDoc "The teleport package to use";
+      package = mkPackageOption pkgs "teleport" {
+        example = "teleport_11";
       };
 
       settings = mkOption {
diff --git a/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix
index 321e46fb5d4d3..92da2e6c254bb 100644
--- a/nixos/modules/services/networking/thelounge.nix
+++ b/nixos/modules/services/networking/thelounge.nix
@@ -25,7 +25,7 @@ in
   options.services.thelounge = {
     enable = mkEnableOption (lib.mdDoc "The Lounge web IRC client");
 
-    package = mkPackageOptionMD pkgs "thelounge" { };
+    package = mkPackageOption pkgs "thelounge" { };
 
     public = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index 7db83e6a584ba..eb769f53901cf 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -279,14 +279,7 @@ in
               '';
             };
 
-            package = mkOption {
-              type = types.package;
-              default = pkgs.tinc_pre;
-              defaultText = literalExpression "pkgs.tinc_pre";
-              description = lib.mdDoc ''
-                The package to use for the tinc daemon's binary.
-              '';
-            };
+            package = mkPackageOption pkgs "tinc_pre" { };
 
             chroot = mkOption {
               default = false;
diff --git a/nixos/modules/services/networking/tinyproxy.nix b/nixos/modules/services/networking/tinyproxy.nix
new file mode 100644
index 0000000000000..8ff12b52f10ca
--- /dev/null
+++ b/nixos/modules/services/networking/tinyproxy.nix
@@ -0,0 +1,103 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.tinyproxy;
+  mkValueStringTinyproxy = with lib; v:
+        if true  ==         v then "yes"
+        else if false ==    v then "no"
+        else generators.mkValueStringDefault {} v;
+  mkKeyValueTinyproxy = {
+    mkValueString ? mkValueStringDefault {}
+  }: sep: k: v:
+    if null     ==  v then ""
+    else "${lib.strings.escape [sep] k}${sep}${mkValueString v}";
+
+  settingsFormat = (pkgs.formats.keyValue {
+      mkKeyValue = mkKeyValueTinyproxy {
+        mkValueString = mkValueStringTinyproxy;
+      } " ";
+      listsAsDuplicateKeys= true;
+  });
+  configFile = settingsFormat.generate "tinyproxy.conf" cfg.settings;
+
+in
+{
+
+  options = {
+    services.tinyproxy = {
+      enable = mkEnableOption (lib.mdDoc "Tinyproxy daemon");
+      package = mkPackageOption pkgs "tinyproxy" {};
+      settings = mkOption {
+        description = lib.mdDoc "Configuration for [tinyproxy](https://tinyproxy.github.io/).";
+        default = { };
+        example = literalExpression ''{
+          Port 8888;
+          Listen 127.0.0.1;
+          Timeout 600;
+          Allow 127.0.0.1;
+          Anonymous = ['"Host"' '"Authorization"'];
+          ReversePath = '"/example/" "http://www.example.com/"';
+        }'';
+        type = types.submodule ({name, ...}: {
+          freeformType = settingsFormat.type;
+          options = {
+            Listen = mkOption {
+              type = types.str;
+              default = "127.0.0.1";
+              description = lib.mdDoc ''
+              Specify which address to listen to.
+              '';
+            };
+            Port = mkOption {
+              type = types.int;
+              default = 8888;
+              description = lib.mdDoc ''
+              Specify which port to listen to.
+              '';
+            };
+            Anonymous = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = lib.mdDoc ''
+              If an `Anonymous` keyword is present, then anonymous proxying is enabled. The headers listed with `Anonymous` are allowed through, while all others are denied. If no Anonymous keyword is present, then all headers are allowed through. You must include quotes around the headers.
+              '';
+            };
+            Filter = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = lib.mdDoc ''
+              Tinyproxy supports filtering of web sites based on URLs or domains. This option specifies the location of the file containing the filter rules, one rule per line.
+              '';
+            };
+          };
+        });
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+    systemd.services.tinyproxy = {
+      description = "TinyProxy daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = "tinyproxy";
+        Group = "tinyproxy";
+        Type = "simple";
+        ExecStart = "${getExe cfg.package} -d -c ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        KillSignal = "SIGINT";
+        TimeoutStopSec = "30s";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users.tinyproxy = {
+        group = "tinyproxy";
+        isSystemUser = true;
+    };
+    users.groups.tinyproxy = {};
+  };
+  meta.maintainers = with maintainers; [ tcheronneau ];
+}
diff --git a/nixos/modules/services/networking/tmate-ssh-server.nix b/nixos/modules/services/networking/tmate-ssh-server.nix
index ff4ce0773309f..6bee2721f9a72 100644
--- a/nixos/modules/services/networking/tmate-ssh-server.nix
+++ b/nixos/modules/services/networking/tmate-ssh-server.nix
@@ -18,12 +18,7 @@ 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;
-    };
+    package = mkPackageOption pkgs "tmate-ssh-server" { };
 
     host = mkOption {
       type = types.str;
@@ -81,12 +76,12 @@ in
       [
         (pkgs.writeShellApplication {
           name = "tmate-client-config";
-          runtimeInputs = with pkgs;[ openssh coreutils sd ];
+          runtimeInputs = with pkgs;[ openssh coreutils ];
           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"
+            sed "s|@ed25519_fingerprint@|$ED25519_SIG|g" ${tmate-config} | \
+              sed "s|@rsa_fingerprint@|$RSA_SIG|g"
           '';
         })
       ];
diff --git a/nixos/modules/services/networking/tox-bootstrapd.nix b/nixos/modules/services/networking/tox-bootstrapd.nix
index 5c7e7a4c22082..0f310a28d266d 100644
--- a/nixos/modules/services/networking/tox-bootstrapd.nix
+++ b/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -47,7 +47,7 @@ in
               lib.mdDoc ''
                 Configuration for bootstrap daemon.
                 See <https://github.com/irungentoo/toxcore/blob/master/other/bootstrap_daemon/tox-bootstrapd.conf>
-                and <http://wiki.tox.im/Nodes>.
+                and <https://wiki.tox.chat/users/nodes>.
              '';
           };
       };
diff --git a/nixos/modules/services/networking/trickster.nix b/nixos/modules/services/networking/trickster.nix
index 0b696e412b4d9..4b920ec446e04 100644
--- a/nixos/modules/services/networking/trickster.nix
+++ b/nixos/modules/services/networking/trickster.nix
@@ -20,14 +20,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.trickster;
-        defaultText = literalExpression "pkgs.trickster";
-        description = lib.mdDoc ''
-          Package that should be used for trickster.
-        '';
-      };
+      package = mkPackageOption pkgs "trickster" { };
 
       configFile = mkOption {
         type = types.nullOr types.path;
diff --git a/nixos/modules/services/networking/trust-dns.nix b/nixos/modules/services/networking/trust-dns.nix
index a3b4d12479b4e..47020341024b5 100644
--- a/nixos/modules/services/networking/trust-dns.nix
+++ b/nixos/modules/services/networking/trust-dns.nix
@@ -1,5 +1,4 @@
 { config, lib, pkgs, ... }:
-
 let
   cfg = config.services.trust-dns;
   toml = pkgs.formats.toml { };
@@ -49,13 +48,11 @@ in
   options = {
     services.trust-dns = with lib; {
       enable = mkEnableOption (lib.mdDoc "trust-dns");
-      package = mkOption {
-        type = types.package;
-        default = pkgs.trust-dns;
-        defaultText = "pkgs.trust-dns";
-        description = mdDoc ''
-          Trust-dns package to use.
-          Only `bin/named` need be provided: the other trust-dns utilities (client and resolver) are not needed.
+      package = mkPackageOption pkgs "trust-dns" {
+        extraDescription = ''
+          ::: {.note}
+          The package must provide `meta.mainProgram` which names the server binayr; any other utilities (client, resolver) are not needed.
+          :::
         '';
       };
       quiet = mkOption {
@@ -136,7 +133,7 @@ in
           flags =  (lib.optional cfg.debug "--debug") ++ (lib.optional cfg.quiet "--quiet");
           flagsStr = builtins.concatStringsSep " " flags;
         in ''
-          ${cfg.package}/bin/named --config ${configFile} ${flagsStr}
+          ${cfg.package}/bin/${cfg.package.meta.mainProgram} --config ${configFile} ${flagsStr}
         '';
         Type = "simple";
         Restart = "on-failure";
diff --git a/nixos/modules/services/networking/twingate.nix b/nixos/modules/services/networking/twingate.nix
index 03c68fc874f02..6874b1c18b573 100644
--- a/nixos/modules/services/networking/twingate.nix
+++ b/nixos/modules/services/networking/twingate.nix
@@ -6,7 +6,7 @@ in
 {
   options.services.twingate = {
     enable = lib.mkEnableOption (lib.mdDoc "Twingate Client daemon");
-    package = lib.mkPackageOptionMD pkgs "twingate" { };
+    package = lib.mkPackageOption pkgs "twingate" { };
   };
 
   config = lib.mkIf cfg.enable {
diff --git a/nixos/modules/services/networking/ucarp.nix b/nixos/modules/services/networking/ucarp.nix
index 1214cec63f54d..56799fe00adef 100644
--- a/nixos/modules/services/networking/ucarp.nix
+++ b/nixos/modules/services/networking/ucarp.nix
@@ -143,16 +143,11 @@ in {
       default = null;
     };
 
-    package = mkOption {
-      type = types.package;
-      description = lib.mdDoc ''
-        Package that should be used for ucarp.
-
+    package = mkPackageOption pkgs "ucarp" {
+      extraDescription = ''
         Please note that the default package, pkgs.ucarp, has not received any
         upstream updates for a long time and can be considered as unmaintained.
       '';
-      default = pkgs.ucarp;
-      defaultText = literalExpression "pkgs.ucarp";
     };
   };
 
diff --git a/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 0426dbb0c83c3..616b32f117979 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -42,12 +42,7 @@ in {
 
       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 = lib.mdDoc "The unbound package to use";
-      };
+      package = mkPackageOption pkgs "unbound-with-systemd" { };
 
       user = mkOption {
         type = types.str;
@@ -166,7 +161,7 @@ in {
     services.unbound.settings = {
       server = {
         directory = mkDefault cfg.stateDir;
-        username = cfg.user;
+        username = ''""'';
         chroot = ''""'';
         pidfile = ''""'';
         # when running under systemd there is no need to daemonize
@@ -245,14 +240,13 @@ in {
         NotifyAccess = "main";
         Type = "notify";
 
-        # FIXME: Which of these do we actually need, can we drop the chroot flag?
         AmbientCapabilities = [
           "CAP_NET_BIND_SERVICE"
+          "CAP_NET_RAW" # needed if ip-transparent is set to true
+        ];
+        CapabilityBoundingSet = [
+          "CAP_NET_BIND_SERVICE"
           "CAP_NET_RAW"
-          "CAP_SETGID"
-          "CAP_SETUID"
-          "CAP_SYS_CHROOT"
-          "CAP_SYS_RESOURCE"
         ];
 
         User = cfg.user;
@@ -266,22 +260,19 @@ in {
         ProtectControlGroups = true;
         ProtectKernelModules = true;
         ProtectSystem = "strict";
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+        ProtectKernelLogs = true;
+        ProtectKernelTunables = true;
         RuntimeDirectory = "unbound";
         ConfigurationDirectory = "unbound";
         StateDirectory = "unbound";
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" "AF_UNIX" ];
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
-        SystemCallFilter = [
-          "~@clock"
-          "@cpu-emulation"
-          "@debug"
-          "@keyring"
-          "@module"
-          "mount"
-          "@obsolete"
-          "@resources"
-        ];
+        SystemCallFilter = [ "@system-service" ];
         RestrictNamespaces = true;
         LockPersonality = true;
         RestrictSUIDSGID = true;
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index 3579d67aa54b7..8eb29f2bcdb6f 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -1,56 +1,54 @@
 { config, options, lib, pkgs, utils, ... }:
-with lib;
 let
   cfg = config.services.unifi;
   stateDir = "/var/lib/unifi";
-  cmd = ''
-    @${cfg.jrePackage}/bin/java java \
-        ${optionalString (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m"} \
-        ${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \
-        -jar ${stateDir}/lib/ace.jar
-  '';
+  cmd = lib.escapeShellArgs ([ "@${cfg.jrePackage}/bin/java" "java" ]
+    ++ lib.optionals (lib.versionAtLeast (lib.getVersion cfg.jrePackage) "16") [
+      "--add-opens=java.base/java.lang=ALL-UNNAMED"
+      "--add-opens=java.base/java.time=ALL-UNNAMED"
+      "--add-opens=java.base/sun.security.util=ALL-UNNAMED"
+      "--add-opens=java.base/java.io=ALL-UNNAMED"
+      "--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED"
+    ]
+    ++ (lib.optional (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m")
+    ++ (lib.optional (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m")
+    ++ cfg.extraJvmOptions
+    ++ [ "-jar" "${stateDir}/lib/ace.jar" ]);
 in
 {
 
   options = {
 
-    services.unifi.enable = mkOption {
-      type = types.bool;
+    services.unifi.enable = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = lib.mdDoc ''
         Whether or not to enable the unifi controller service.
       '';
     };
 
-    services.unifi.jrePackage = mkOption {
-      type = types.package;
-      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'';
+    services.unifi.jrePackage = lib.mkOption {
+      type = lib.types.package;
+      default = if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.3") then pkgs.jdk11 else pkgs.jre8;
+      defaultText = lib.literalExpression ''if (lib.versionAtLeast (lib.getVersion cfg.unifiPackage) "7.5") then pkgs.jdk17_headless else 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.
       '';
     };
 
-    services.unifi.unifiPackage = mkOption {
-      type = types.package;
-      default = pkgs.unifi5;
-      defaultText = literalExpression "pkgs.unifi5";
-      description = lib.mdDoc ''
-        The unifi package to use.
-      '';
-    };
+    services.unifi.unifiPackage = lib.mkPackageOption pkgs "unifi5" { };
 
-    services.unifi.mongodbPackage = mkOption {
-      type = types.package;
-      default = pkgs.mongodb-4_4;
-      defaultText = literalExpression "pkgs.mongodb";
-      description = lib.mdDoc ''
-        The mongodb package to use. Please note: unifi7 officially only supports mongodb up until 3.6 but works with 4.4.
+    services.unifi.mongodbPackage = lib.mkPackageOption pkgs "mongodb" {
+      default = "mongodb-4_4";
+      extraDescription = ''
+        ::: {.note}
+        unifi7 officially only supports mongodb up until 3.6 but works with 4.4.
+        :::
       '';
     };
 
-    services.unifi.openFirewall = mkOption {
-      type = types.bool;
+    services.unifi.openFirewall = lib.mkOption {
+      type = lib.types.bool;
       default = false;
       description = lib.mdDoc ''
         Whether or not to open the minimum required ports on the firewall.
@@ -61,8 +59,8 @@ in
       '';
     };
 
-    services.unifi.initialJavaHeapSize = mkOption {
-      type = types.nullOr types.int;
+    services.unifi.initialJavaHeapSize = lib.mkOption {
+      type = with lib.types; nullOr int;
       default = null;
       example = 1024;
       description = lib.mdDoc ''
@@ -71,8 +69,8 @@ in
       '';
     };
 
-    services.unifi.maximumJavaHeapSize = mkOption {
-      type = types.nullOr types.int;
+    services.unifi.maximumJavaHeapSize = lib.mkOption {
+      type = with lib.types; nullOr int;
       default = null;
       example = 4096;
       description = lib.mdDoc ''
@@ -81,9 +79,18 @@ in
       '';
     };
 
+    services.unifi.extraJvmOptions = lib.mkOption {
+      type = with lib.types; listOf str;
+      default = [ ];
+      example = lib.literalExpression ''["-Xlog:gc"]'';
+      description = lib.mdDoc ''
+        Set extra options to pass to the JVM.
+      '';
+    };
+
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
 
     users.users.unifi = {
       isSystemUser = true;
@@ -93,7 +100,7 @@ in
     };
     users.groups.unifi = {};
 
-    networking.firewall = mkIf cfg.openFirewall {
+    networking.firewall = lib.mkIf cfg.openFirewall {
       # https://help.ubnt.com/hc/en-us/articles/218506997
       allowedTCPPorts = [
         8080  # Port for UAP to inform controller.
@@ -119,8 +126,8 @@ in
 
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${(removeSuffix "\n" cmd)} start";
-        ExecStop = "${(removeSuffix "\n" cmd)} stop";
+        ExecStart = "${cmd} start";
+        ExecStop = "${cmd} stop";
         Restart = "on-failure";
         TimeoutSec = "5min";
         User = "unifi";
@@ -162,7 +169,7 @@ in
         StateDirectory = "unifi";
         RuntimeDirectory = "unifi";
         LogsDirectory = "unifi";
-        CacheDirectory= "unifi";
+        CacheDirectory = "unifi";
 
         TemporaryFileSystem = [
           # required as we want to create bind mounts below
@@ -172,7 +179,7 @@ in
         # We must create the binary directories as bind mounts instead of symlinks
         # This is because the controller resolves all symlinks to absolute paths
         # to be used as the working directory.
-        BindPaths =  [
+        BindPaths = [
           "/var/log/unifi:${stateDir}/logs"
           "/run/unifi:${stateDir}/run"
           "${cfg.unifiPackage}/dl:${stateDir}/dl"
@@ -190,7 +197,7 @@ in
 
   };
   imports = [
-    (mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data" )
-    (mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ])
+    (lib.mkRemovedOptionModule [ "services" "unifi" "dataDir" ] "You should move contents of dataDir to /var/lib/unifi/data")
+    (lib.mkRenamedOptionModule [ "services" "unifi" "openPorts" ] [ "services" "unifi" "openFirewall" ])
   ];
 }
diff --git a/nixos/modules/services/networking/v2ray.nix b/nixos/modules/services/networking/v2ray.nix
index ba2aa5bc1de77..3e1895fbe20c4 100644
--- a/nixos/modules/services/networking/v2ray.nix
+++ b/nixos/modules/services/networking/v2ray.nix
@@ -16,14 +16,7 @@ with lib;
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.v2ray;
-        defaultText = literalExpression "pkgs.v2ray";
-        description = lib.mdDoc ''
-          Which v2ray package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "v2ray" { };
 
       configFile = mkOption {
         type = types.nullOr types.str;
diff --git a/nixos/modules/services/networking/vdirsyncer.nix b/nixos/modules/services/networking/vdirsyncer.nix
index f9b880c763e3d..165dc70f0876a 100644
--- a/nixos/modules/services/networking/vdirsyncer.nix
+++ b/nixos/modules/services/networking/vdirsyncer.nix
@@ -20,9 +20,11 @@ let
     else
       pkgs.writeText "vdirsyncer-${name}.conf" (toIniJson (
         {
-          general = cfg'.config.general // (lib.optionalAttrs (cfg'.config.statusPath == null) {
-            status_path = "/var/lib/vdirsyncer/${name}";
-          });
+          general = cfg'.config.general // {
+            status_path = if cfg'.config.statusPath == null
+                          then "/var/lib/vdirsyncer/${name}"
+                          else cfg'.config.statusPath;
+          };
         } // (
           mapAttrs' (name: nameValuePair "pair ${name}") cfg'.config.pairs
         ) // (
@@ -71,7 +73,7 @@ in
     services.vdirsyncer = {
       enable = mkEnableOption (mdDoc "vdirsyncer");
 
-      package = mkPackageOptionMD pkgs "vdirsyncer" {};
+      package = mkPackageOption pkgs "vdirsyncer" {};
 
       jobs = mkOption {
         description = mdDoc "vdirsyncer job configurations";
diff --git a/nixos/modules/services/networking/wasabibackend.nix b/nixos/modules/services/networking/wasabibackend.nix
index 938145b35ee88..e3a48afd2a2c5 100644
--- a/nixos/modules/services/networking/wasabibackend.nix
+++ b/nixos/modules/services/networking/wasabibackend.nix
@@ -119,6 +119,7 @@ in {
     systemd.services.wasabibackend = {
       description = "wasabibackend server";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       environment = {
         DOTNET_PRINT_TELEMETRY_MESSAGE = "false";
diff --git a/nixos/modules/services/networking/webhook.nix b/nixos/modules/services/networking/webhook.nix
index 2a78491941cf9..b020db6961c32 100644
--- a/nixos/modules/services/networking/webhook.nix
+++ b/nixos/modules/services/networking/webhook.nix
@@ -36,7 +36,7 @@ in {
         which execute configured commands for any person or service that knows the URL
       '');
 
-      package = mkPackageOptionMD pkgs "webhook" {};
+      package = mkPackageOption pkgs "webhook" {};
       user = mkOption {
         type = types.str;
         default = defaultUser;
diff --git a/nixos/modules/services/networking/websockify.nix b/nixos/modules/services/networking/websockify.nix
index 45a3487bd3370..27ad8953d3faf 100644
--- a/nixos/modules/services/networking/websockify.nix
+++ b/nixos/modules/services/networking/websockify.nix
@@ -38,7 +38,7 @@ let cfg = config.services.networking.websockify; in {
       description = "Service to forward websocket connections to TCP connections (from port:to port %I)";
       script = ''
         IFS=':' read -a array <<< "$1"
-        ${pkgs.pythonPackages.websockify}/bin/websockify --ssl-only \
+        ${pkgs.python3Packages.websockify}/bin/websockify --ssl-only \
           --cert=${cfg.sslCert} --key=${cfg.sslKey} 0.0.0.0:''${array[0]} 0.0.0.0:''${array[1]}
       '';
       scriptArgs = "%i";
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
index 34210580f538a..68e0e06d0469d 100644
--- a/nixos/modules/services/networking/wg-quick.nix
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -17,6 +17,8 @@ let
         type = with types; nullOr str;
         description = lib.mdDoc ''
           wg-quick .conf file, describing the interface.
+          Using this option can be a useful means of configuring WireGuard if
+          one has an existing .conf file.
           This overrides any other configuration interface configuration options.
           See wg-quick manpage for more details.
         '';
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 21473388d76e1..d36be87daf60f 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -164,6 +164,15 @@ let
           of the wireguard network has to be adjusted as well.
         '';
       };
+
+      metric = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 700;
+        description = lib.mdDoc ''
+          Set the metric of routes related to this Wireguard interface.
+        '';
+      };
     };
 
   };
@@ -395,7 +404,7 @@ let
             optionalString interfaceCfg.allowedIPsAsRoutes
               (concatMapStringsSep "\n"
                 (allowedIP:
-                  ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"''
+                  ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}" ${optionalString (interfaceCfg.metric != null) "metric ${toString interfaceCfg.metric}"}''
                 ) peer.allowedIPs);
         in ''
           ${wg_setup}
@@ -577,6 +586,7 @@ in
         }) all_peers;
 
     boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
+    boot.kernelModules = [ "wireguard" ];
     environment.systemPackages = [ pkgs.wireguard-tools ];
 
     systemd.services =
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 0595e9e6df238..4586550ed75e7 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -107,6 +107,10 @@ let
       stopIfChanged = false;
 
       path = [ package ];
+      # if `userControl.enable`, the supplicant automatically changes the permissions
+      #  and owning group of the runtime dir; setting `umask` ensures the generated
+      #  config file isn't readable (except to root);  see nixpkgs#267693
+      serviceConfig.UMask = "066";
       serviceConfig.RuntimeDirectory = "wpa_supplicant";
       serviceConfig.RuntimeDirectoryMode = "700";
       serviceConfig.EnvironmentFile = mkIf (cfg.environmentFile != null)
@@ -530,5 +534,5 @@ in {
     '';
   };
 
-  meta.maintainers = with lib.maintainers; [ globin rnhmjoj ];
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
 }
diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix
index 067d5df487255..2762c85651f46 100644
--- a/nixos/modules/services/networking/wstunnel.nix
+++ b/nixos/modules/services/networking/wstunnel.nix
@@ -48,7 +48,7 @@ let
       default = true;
     };
 
-    package = mkPackageOptionMD pkgs "wstunnel" {};
+    package = mkPackageOption pkgs "wstunnel" {};
 
     autoStart = mkOption {
       description = mdDoc "Whether this tunnel server should be started automatically.";
@@ -86,12 +86,12 @@ let
         description = mdDoc "Address and port to listen on. Setting the port to a value below 1024 will also give the process the required `CAP_NET_BIND_SERVICE` capability.";
         type = types.submodule hostPortSubmodule;
         default = {
-          address = "0.0.0.0";
+          host = "0.0.0.0";
           port = if config.enableHTTPS then 443 else 80;
         };
         defaultText = literalExpression ''
           {
-            address = "0.0.0.0";
+            host = "0.0.0.0";
             port = if enableHTTPS then 443 else 80;
           }
         '';
diff --git a/nixos/modules/services/networking/x2goserver.nix b/nixos/modules/services/networking/x2goserver.nix
index 1242229a0b60f..f1eba9fafc1c2 100644
--- a/nixos/modules/services/networking/x2goserver.nix
+++ b/nixos/modules/services/networking/x2goserver.nix
@@ -160,5 +160,8 @@ in {
     security.sudo.extraConfig = ''
       Defaults  env_keep+=QT_GRAPHICSSYSTEM
     '';
+    security.sudo-rs.extraConfig = ''
+      Defaults  env_keep+=QT_GRAPHICSSYSTEM
+    '';
   };
 }
diff --git a/nixos/modules/services/networking/xandikos.nix b/nixos/modules/services/networking/xandikos.nix
index 6d1ddc74c719a..147f07ac546de 100644
--- a/nixos/modules/services/networking/xandikos.nix
+++ b/nixos/modules/services/networking/xandikos.nix
@@ -11,12 +11,7 @@ in
     services.xandikos = {
       enable = mkEnableOption (lib.mdDoc "Xandikos CalDAV and CardDAV server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.xandikos;
-        defaultText = literalExpression "pkgs.xandikos";
-        description = lib.mdDoc "The Xandikos package to use.";
-      };
+      package = mkPackageOption pkgs "xandikos" { };
 
       address = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/networking/xray.nix b/nixos/modules/services/networking/xray.nix
index 83655a2f88ef4..56c7887b3308e 100644
--- a/nixos/modules/services/networking/xray.nix
+++ b/nixos/modules/services/networking/xray.nix
@@ -16,14 +16,7 @@ with lib;
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.xray;
-        defaultText = literalExpression "pkgs.xray";
-        description = lib.mdDoc ''
-          Which xray package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "xray" { };
 
       settingsFile = mkOption {
         type = types.nullOr types.path;
diff --git a/nixos/modules/services/networking/xrdp.nix b/nixos/modules/services/networking/xrdp.nix
index 218b440aab3c6..7e6634cd239aa 100644
--- a/nixos/modules/services/networking/xrdp.nix
+++ b/nixos/modules/services/networking/xrdp.nix
@@ -4,14 +4,17 @@ with lib;
 
 let
   cfg = config.services.xrdp;
+
   confDir = pkgs.runCommand "xrdp.conf" { preferLocalBuild = true; } ''
-    mkdir $out
+    mkdir -p $out
 
-    cp ${cfg.package}/etc/xrdp/{km-*,xrdp,sesman,xrdp_keyboard}.ini $out
+    cp -r ${cfg.package}/etc/xrdp/* $out
+    chmod -R +w $out
 
     cat > $out/startwm.sh <<EOF
     #!/bin/sh
     . /etc/profile
+    ${lib.optionalString cfg.audio.enable "${cfg.audio.package}/libexec/pulsaudio-xrdp-module/pulseaudio_xrdp_init"}
     ${cfg.defaultWindowManager}
     EOF
     chmod +x $out/startwm.sh
@@ -25,13 +28,17 @@ let
 
     substituteInPlace $out/sesman.ini \
       --replace LogFile=xrdp-sesman.log LogFile=/dev/null \
-      --replace EnableSyslog=1 EnableSyslog=0
+      --replace EnableSyslog=1 EnableSyslog=0 \
+      --replace startwm.sh $out/startwm.sh \
+      --replace reconnectwm.sh $out/reconnectwm.sh \
 
     # Ensure that clipboard works for non-ASCII characters
     sed -i -e '/.*SessionVariables.*/ a\
     LANG=${config.i18n.defaultLocale}\
     LOCALE_ARCHIVE=${config.i18n.glibcLocales}/lib/locale/locale-archive
     ' $out/sesman.ini
+
+    ${cfg.extraConfDirCommands}
   '';
 in
 {
@@ -44,13 +51,11 @@ in
 
       enable = mkEnableOption (lib.mdDoc "xrdp, the Remote Desktop Protocol server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.xrdp;
-        defaultText = literalExpression "pkgs.xrdp";
-        description = lib.mdDoc ''
-          The package to use for the xrdp daemon's binary.
-        '';
+      package = mkPackageOptionMD pkgs "xrdp" { };
+
+      audio = {
+        enable = mkEnableOption (lib.mdDoc "audio support for xrdp sessions. So far it only works with PulseAudio sessions on the server side. No PipeWire support yet");
+        package = mkPackageOptionMD pkgs "pulseaudio-module-xrdp" {};
       };
 
       port = mkOption {
@@ -100,86 +105,117 @@ in
       confDir = mkOption {
         type = types.path;
         default = confDir;
-        defaultText = literalMD "generated from configuration";
-        description = lib.mdDoc "The location of the config files for xrdp.";
+        internal = true;
+        description = lib.mdDoc ''
+          Configuration directory of xrdp and sesman.
+
+          Changes to this must be made through extraConfDirCommands.
+        '';
+        readOnly = true;
+      };
+
+      extraConfDirCommands = mkOption {
+        type = types.str;
+        default = "";
+        description = lib.mdDoc ''
+          Extra commands to run on the default confDir derivation.
+        '';
+        example = ''
+          substituteInPlace $out/sesman.ini \
+            --replace LogLevel=INFO LogLevel=DEBUG \
+            --replace LogFile=/dev/null LogFile=/var/log/xrdp.log
+        '';
       };
     };
   };
 
-
   ###### implementation
 
-  config = mkIf cfg.enable {
+  config = lib.mkMerge [
+    (mkIf cfg.audio.enable {
+      environment.systemPackages = [ cfg.audio.package ];  # needed for autostart
 
-    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+      hardware.pulseaudio.extraModules = [ cfg.audio.package ];
+    })
 
-    # xrdp can run X11 program even if "services.xserver.enable = false"
-    xdg = {
-      autostart.enable = true;
-      menus.enable = true;
-      mime.enable = true;
-      icons.enable = true;
-    };
+    (mkIf cfg.enable {
 
-    fonts.enableDefaultPackages = mkDefault true;
-
-    systemd = {
-      services.xrdp = {
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
-        description = "xrdp daemon";
-        requires = [ "xrdp-sesman.service" ];
-        preStart = ''
-          # prepare directory for unix sockets (the sockets will be owned by loggedinuser:xrdp)
-          mkdir -p /tmp/.xrdp || true
-          chown xrdp:xrdp /tmp/.xrdp
-          chmod 3777 /tmp/.xrdp
-
-          # generate a self-signed certificate
-          if [ ! -s ${cfg.sslCert} -o ! -s ${cfg.sslKey} ]; then
-            mkdir -p $(dirname ${cfg.sslCert}) || true
-            mkdir -p $(dirname ${cfg.sslKey}) || true
-            ${pkgs.openssl.bin}/bin/openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days 365 \
-              -subj /C=US/ST=CA/L=Sunnyvale/O=xrdp/CN=www.xrdp.org \
-              -config ${cfg.package}/share/xrdp/openssl.conf \
-              -keyout ${cfg.sslKey} -out ${cfg.sslCert}
-            chown root:xrdp ${cfg.sslKey} ${cfg.sslCert}
-            chmod 440 ${cfg.sslKey} ${cfg.sslCert}
-          fi
-          if [ ! -s /run/xrdp/rsakeys.ini ]; then
-            mkdir -p /run/xrdp
-            ${cfg.package}/bin/xrdp-keygen xrdp /run/xrdp/rsakeys.ini
-          fi
-        '';
-        serviceConfig = {
-          User = "xrdp";
-          Group = "xrdp";
-          PermissionsStartOnly = true;
-          ExecStart = "${cfg.package}/bin/xrdp --nodaemon --port ${toString cfg.port} --config ${cfg.confDir}/xrdp.ini";
-        };
+      networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+      # xrdp can run X11 program even if "services.xserver.enable = false"
+      xdg = {
+        autostart.enable = true;
+        menus.enable = true;
+        mime.enable = true;
+        icons.enable = true;
       };
 
-      services.xrdp-sesman = {
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
-        description = "xrdp session manager";
-        restartIfChanged = false; # do not restart on "nixos-rebuild switch". like "display-manager", it can have many interactive programs as children
-        serviceConfig = {
-          ExecStart = "${cfg.package}/bin/xrdp-sesman --nodaemon --config ${cfg.confDir}/sesman.ini";
-          ExecStop  = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+      fonts.enableDefaultPackages = mkDefault true;
+
+      environment.etc."xrdp".source = "${confDir}/*";
+
+      systemd = {
+        services.xrdp = {
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          description = "xrdp daemon";
+          requires = [ "xrdp-sesman.service" ];
+          preStart = ''
+            # prepare directory for unix sockets (the sockets will be owned by loggedinuser:xrdp)
+            mkdir -p /tmp/.xrdp || true
+            chown xrdp:xrdp /tmp/.xrdp
+            chmod 3777 /tmp/.xrdp
+
+            # generate a self-signed certificate
+            if [ ! -s ${cfg.sslCert} -o ! -s ${cfg.sslKey} ]; then
+              mkdir -p $(dirname ${cfg.sslCert}) || true
+              mkdir -p $(dirname ${cfg.sslKey}) || true
+              ${lib.getExe pkgs.openssl} req -x509 -newkey rsa:2048 -sha256 -nodes -days 365 \
+                -subj /C=US/ST=CA/L=Sunnyvale/O=xrdp/CN=www.xrdp.org \
+                -config ${cfg.package}/share/xrdp/openssl.conf \
+                -keyout ${cfg.sslKey} -out ${cfg.sslCert}
+              chown root:xrdp ${cfg.sslKey} ${cfg.sslCert}
+              chmod 440 ${cfg.sslKey} ${cfg.sslCert}
+            fi
+            if [ ! -s /run/xrdp/rsakeys.ini ]; then
+              mkdir -p /run/xrdp
+              ${pkgs.xrdp}/bin/xrdp-keygen xrdp /run/xrdp/rsakeys.ini
+            fi
+          '';
+          serviceConfig = {
+            User = "xrdp";
+            Group = "xrdp";
+            PermissionsStartOnly = true;
+            ExecStart = "${pkgs.xrdp}/bin/xrdp --nodaemon --port ${toString cfg.port} --config ${confDir}/xrdp.ini";
+          };
+        };
+
+        services.xrdp-sesman = {
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          description = "xrdp session manager";
+          restartIfChanged = false; # do not restart on "nixos-rebuild switch". like "display-manager", it can have many interactive programs as children
+          serviceConfig = {
+            ExecStart = "${pkgs.xrdp}/bin/xrdp-sesman --nodaemon --config ${confDir}/sesman.ini";
+            ExecStop  = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+          };
         };
+
       };
 
-    };
+      users.users.xrdp = {
+        description   = "xrdp daemon user";
+        isSystemUser  = true;
+        group         = "xrdp";
+      };
+      users.groups.xrdp = {};
 
-    users.users.xrdp = {
-      description   = "xrdp daemon user";
-      isSystemUser  = true;
-      group         = "xrdp";
-    };
-    users.groups.xrdp = {};
+      security.pam.services.xrdp-sesman = {
+        allowNullPassword = true;
+        startSession = true;
+      };
 
-    security.pam.services.xrdp-sesman = { allowNullPassword = true; startSession = true; };
-  };
+    })
+  ];
 
 }
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
index 55a6002d61af1..9173e7eb3457b 100644
--- a/nixos/modules/services/networking/yggdrasil.nix
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -108,19 +108,21 @@ in
         '';
       };
 
-      package = mkOption {
-        type = package;
-        default = pkgs.yggdrasil;
-        defaultText = literalExpression "pkgs.yggdrasil";
-        description = lib.mdDoc "Yggdrasil package to use.";
-      };
+      package = mkPackageOption pkgs "yggdrasil" { };
 
       persistentKeys = mkEnableOption (lib.mdDoc ''
-        If enabled then keys will be generated once and Yggdrasil
+        persistent keys. 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}.
+        restarted. Keys are stored at ${keysPath}
       '');
 
+      extraArgs = mkOption {
+        type = listOf str;
+        default = [ ];
+        example = [ "-loglevel" "info" ];
+        description = lib.mdDoc "Extra command line arguments.";
+      };
+
     };
   };
 
@@ -135,16 +137,24 @@ in
         message = "networking.enableIPv6 must be true for yggdrasil to work";
       }];
 
-      system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
-        if [ ! -e ${keysPath} ]
-        then
-          mkdir --mode=700 -p ${builtins.dirOf keysPath}
-          ${binYggdrasil} -genconf -json \
-            | ${pkgs.jq}/bin/jq \
-                'to_entries|map(select(.key|endswith("Key")))|from_entries' \
-            > ${keysPath}
-        fi
-      '';
+      # This needs to be a separate service. The yggdrasil service fails if
+      # this is put into its preStart.
+      systemd.services.yggdrasil-persistent-keys = lib.mkIf cfg.persistentKeys {
+        wantedBy = [ "multi-user.target" ];
+        before = [ "yggdrasil.service" ];
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        script = ''
+          if [ ! -e ${keysPath} ]
+          then
+            mkdir --mode=700 -p ${builtins.dirOf keysPath}
+            ${binYggdrasil} -genconf -json \
+              | ${pkgs.jq}/bin/jq \
+                  'to_entries|map(select(.key|endswith("Key")))|from_entries' \
+              > ${keysPath}
+          fi
+        '';
+      };
 
       systemd.services.yggdrasil = {
         description = "Yggdrasil Network Service";
@@ -181,7 +191,7 @@ in
             "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf"}
 
           # start yggdrasil
-          ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf
+          ${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf ${lib.strings.escapeShellArgs cfg.extraArgs}
         '';
 
         serviceConfig = {
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 1f3711bd0d721..7e88a8b346d9a 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -1,7 +1,8 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) generators literalExpression mkEnableOption mkIf mkOption recursiveUpdate types;
+  inherit (lib) generators literalExpression mkEnableOption mkPackageOption
+                mkIf mkOption recursiveUpdate types;
   cfg = config.services.zeronet;
   dataDir = "/var/lib/zeronet";
   configFile = pkgs.writeText "zeronet.conf" (generators.toINI {} (recursiveUpdate defaultSettings cfg.settings));
@@ -19,12 +20,7 @@ in with lib; {
   options.services.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";
-    };
+    package = mkPackageOption pkgs "zeronet" { };
 
     settings = mkOption {
       type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
diff --git a/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix
index f78fd8642ba09..60615d553041b 100644
--- a/nixos/modules/services/networking/zerotierone.nix
+++ b/nixos/modules/services/networking/zerotierone.nix
@@ -4,6 +4,8 @@ with lib;
 
 let
   cfg = config.services.zerotierone;
+  localConfFile = pkgs.writeText "zt-local.conf" (builtins.toJSON cfg.localConf);
+  localConfFilePath = "/var/lib/zerotier-one/local.conf";
 in
 {
   options.services.zerotierone.enable = mkEnableOption (lib.mdDoc "ZeroTierOne");
@@ -27,13 +29,19 @@ in
     '';
   };
 
-  options.services.zerotierone.package = mkOption {
-    default = pkgs.zerotierone;
-    defaultText = literalExpression "pkgs.zerotierone";
-    type = types.package;
-    description = lib.mdDoc ''
-      ZeroTier One package to use.
+  options.services.zerotierone.package = mkPackageOption pkgs "zerotierone" { };
+
+  options.services.zerotierone.localConf = mkOption {
+    default = null;
+    description = mdDoc ''
+      Optional configuration to be written to the Zerotier JSON-based local.conf.
+      If set, the configuration will be symlinked to `/var/lib/zerotier-one/local.conf` at build time.
+      To understand the configuration format, refer to https://docs.zerotier.com/config/#local-configuration-options.
     '';
+    example = {
+      settings.allowTcpFallbackRelay = false;
+    };
+    type = types.nullOr types.attrs;
   };
 
   config = mkIf cfg.enable {
@@ -52,7 +60,17 @@ in
         chown -R root:root /var/lib/zerotier-one
       '' + (concatMapStrings (netId: ''
         touch "/var/lib/zerotier-one/networks.d/${netId}.conf"
-      '') cfg.joinNetworks);
+      '') cfg.joinNetworks) + optionalString (cfg.localConf != null) ''
+        if [ -L "${localConfFilePath}" ]
+        then
+          rm ${localConfFilePath}
+        elif [ -f "${localConfFilePath}" ]
+        then
+          mv ${localConfFilePath} ${localConfFilePath}.bak
+        fi
+        ln -s ${localConfFile} ${localConfFilePath}
+      '';
+
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/zerotier-one -p${toString cfg.port}";
         Restart = "always";
diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix
index d3ba4a524197d..e15233293cf25 100644
--- a/nixos/modules/services/networking/znc/default.nix
+++ b/nixos/modules/services/networking/znc/default.nix
@@ -243,6 +243,7 @@ in
     systemd.services.znc = {
       description = "ZNC Server";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       serviceConfig = {
         User = cfg.user;
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 279b26bb89573..1f044384a5b83 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -4,9 +4,10 @@ with lib;
 
 let
 
-  inherit (pkgs) cups cups-pk-helper cups-filters xdg-utils;
+  inherit (pkgs) cups-pk-helper cups-filters xdg-utils;
 
   cfg = config.services.printing;
+  cups = cfg.package;
 
   avahiEnabled = config.services.avahi.enable;
   polkitEnabled = config.security.polkit.enable;
@@ -108,6 +109,12 @@ let
   containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0;
   getGutenprint = pkgs: head (filterGutenprint pkgs);
 
+  parsePorts = addresses: let
+    splitAddress = addr: strings.splitString ":" addr;
+    extractPort = addr: builtins.foldl' (a: b: b) "" (splitAddress addr);
+  in
+    builtins.map (address: strings.toInt (extractPort address)) addresses;
+
 in
 
 {
@@ -134,6 +141,8 @@ in
         '';
       };
 
+      package = lib.mkPackageOption pkgs "cups" {};
+
       stateless = mkOption {
         type = types.bool;
         default = false;
@@ -172,6 +181,15 @@ in
         '';
       };
 
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open the firewall for TCP/UDP ports specified in
+          listenAdrresses option.
+        '';
+      };
+
       bindirCmds = mkOption {
         type = types.lines;
         internal = true;
@@ -463,6 +481,13 @@ in
 
     security.pam.services.cups = {};
 
+    networking.firewall = let
+      listenPorts = parsePorts cfg.listenAddresses;
+    in mkIf cfg.openFirewall {
+      allowedTCPPorts = listenPorts;
+      allowedUDPPorts = listenPorts;
+    };
+
   };
 
   meta.maintainers = with lib.maintainers; [ matthewbauer ];
diff --git a/nixos/modules/services/search/elasticsearch.nix b/nixos/modules/services/search/elasticsearch.nix
index fa1627566ebed..6eebeb8b0a9a5 100644
--- a/nixos/modules/services/search/elasticsearch.nix
+++ b/nixos/modules/services/search/elasticsearch.nix
@@ -50,12 +50,7 @@ in
       type = types.bool;
     };
 
-    package = mkOption {
-      description = lib.mdDoc "Elasticsearch package to use.";
-      default = pkgs.elasticsearch;
-      defaultText = literalExpression "pkgs.elasticsearch";
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "elasticsearch" { };
 
     listenAddress = mkOption {
       description = lib.mdDoc "Elasticsearch listen address.";
diff --git a/nixos/modules/services/search/hound.nix b/nixos/modules/services/search/hound.nix
index b41a2e2bae1fa..d238b26a226b5 100644
--- a/nixos/modules/services/search/hound.nix
+++ b/nixos/modules/services/search/hound.nix
@@ -3,6 +3,12 @@ with lib;
 let
   cfg = config.services.hound;
 in {
+  imports = [
+    (lib.mkRemovedOptionModule [ "services" "hound" "extraGroups" ] "Use users.users.hound.extraGroups instead")
+  ];
+
+  meta.maintainers = with maintainers; [ SuperSandro2000 ];
+
   options = {
     services.hound = {
       enable = mkOption {
@@ -13,6 +19,8 @@ in {
         '';
       };
 
+      package = mkPackageOptionMD pkgs "hound" { };
+
       user = mkOption {
         default = "hound";
         type = types.str;
@@ -29,31 +37,12 @@ in {
         '';
       };
 
-      extraGroups = mkOption {
-        type = types.listOf types.str;
-        default = [ ];
-        example = [ "dialout" ];
-        description = lib.mdDoc ''
-          List of extra groups that the "hound" user should be a part of.
-        '';
-      };
-
       home = mkOption {
         default = "/var/lib/hound";
         type = types.path;
         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.
-        '';
-      };
-
-      package = mkOption {
-        default = pkgs.hound;
-        defaultText = literalExpression "pkgs.hound";
-        type = types.package;
-        description = lib.mdDoc ''
-          Package for running hound.
+          The path to use as hound's $HOME.
+          If the default user "hound" is configured then this is the home of the "hound" user.
         '';
       };
 
@@ -64,63 +53,62 @@ in {
           should be an absolute path to a writable location on disk.
         '';
         example = literalExpression ''
-          '''
-            {
-              "max-concurrent-indexers" : 2,
-              "dbpath" : "''${services.hound.home}/data",
-              "repos" : {
-                  "nixpkgs": {
-                    "url" : "https://www.github.com/NixOS/nixpkgs.git"
-                  }
-              }
+          {
+            "max-concurrent-indexers" : 2,
+            "repos" : {
+                "nixpkgs": {
+                  "url" : "https://www.github.com/NixOS/nixpkgs.git"
+                }
             }
-          '''
+          }
         '';
       };
 
       listen = mkOption {
         type = types.str;
         default = "0.0.0.0:6080";
-        example = "127.0.0.1:6080 or just :6080";
+        example = ":6080";
         description = lib.mdDoc ''
-          Listen on this IP:port / :port
+          Listen on this [IP]:port
         '';
       };
     };
   };
 
   config = mkIf cfg.enable {
-    users.groups = optionalAttrs (cfg.group == "hound") {
-      hound.gid = config.ids.gids.hound;
+    users.groups = lib.mkIf (cfg.group == "hound") {
+      hound = { };
     };
 
-    users.users = optionalAttrs (cfg.user == "hound") {
+    users.users = lib.mkIf (cfg.user == "hound") {
       hound = {
-        description = "hound code search";
+        description = "Hound code search";
         createHome = true;
-        home = cfg.home;
-        group = cfg.group;
-        extraGroups = cfg.extraGroups;
-        uid = config.ids.uids.hound;
+        isSystemUser = true;
+        inherit (cfg) home group;
       };
     };
 
-    systemd.services.hound = {
+    systemd.services.hound = let
+      configFile = pkgs.writeTextFile {
+        name = "hound.json";
+        text = cfg.config;
+        checkPhase = ''
+          # check if the supplied text is valid json
+          ${lib.getExe pkgs.jq} . $target > /dev/null
+        '';
+      };
+    in {
       description = "Hound Code Search";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
         WorkingDirectory = cfg.home;
         ExecStartPre = "${pkgs.git}/bin/git config --global --replace-all http.sslCAinfo /etc/ssl/certs/ca-certificates.crt";
-        ExecStart = "${cfg.package}/bin/houndd" +
-                    " -addr ${cfg.listen}" +
-                    " -conf ${pkgs.writeText "hound.json" cfg.config}";
-
+        ExecStart = "${cfg.package}/bin/houndd -addr ${cfg.listen} -conf ${configFile}";
       };
     };
   };
-
 }
diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix
deleted file mode 100644
index a5e132d5c38db..0000000000000
--- a/nixos/modules/services/search/kibana.nix
+++ /dev/null
@@ -1,213 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.kibana;
-  opt = options.services.kibana;
-
-  ge7 = builtins.compareVersions cfg.package.version "7" >= 0;
-  lt6_6 = builtins.compareVersions cfg.package.version "6.6" < 0;
-
-  cfgFile = pkgs.writeText "kibana.json" (builtins.toJSON (
-    (filterAttrsRecursive (n: v: v != null && v != []) ({
-      server.host = cfg.listenAddress;
-      server.port = cfg.port;
-      server.ssl.certificate = cfg.cert;
-      server.ssl.key = cfg.key;
-
-      kibana.index = cfg.index;
-      kibana.defaultAppId = cfg.defaultAppId;
-
-      elasticsearch.url = cfg.elasticsearch.url;
-      elasticsearch.hosts = cfg.elasticsearch.hosts;
-      elasticsearch.username = cfg.elasticsearch.username;
-      elasticsearch.password = cfg.elasticsearch.password;
-
-      elasticsearch.ssl.certificate = cfg.elasticsearch.cert;
-      elasticsearch.ssl.key = cfg.elasticsearch.key;
-      elasticsearch.ssl.certificateAuthorities = cfg.elasticsearch.certificateAuthorities;
-    } // cfg.extraConf)
-  )));
-
-in {
-  options.services.kibana = {
-    enable = mkEnableOption (lib.mdDoc "kibana service");
-
-    listenAddress = mkOption {
-      description = lib.mdDoc "Kibana listening host";
-      default = "127.0.0.1";
-      type = types.str;
-    };
-
-    port = mkOption {
-      description = lib.mdDoc "Kibana listening port";
-      default = 5601;
-      type = types.port;
-    };
-
-    cert = mkOption {
-      description = lib.mdDoc "Kibana ssl certificate.";
-      default = null;
-      type = types.nullOr types.path;
-    };
-
-    key = mkOption {
-      description = lib.mdDoc "Kibana ssl key.";
-      default = null;
-      type = types.nullOr types.path;
-    };
-
-    index = mkOption {
-      description = lib.mdDoc "Elasticsearch index to use for saving kibana config.";
-      default = ".kibana";
-      type = types.str;
-    };
-
-    defaultAppId = mkOption {
-      description = lib.mdDoc "Elasticsearch default application id.";
-      default = "discover";
-      type = types.str;
-    };
-
-    elasticsearch = {
-      url = mkOption {
-        description = lib.mdDoc ''
-          Elasticsearch url.
-
-          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`
-          instead.
-        '';
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      hosts = mkOption {
-        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 `[ "http://localhost:9200" ]`.
-
-          This option is only valid when using kibana >= 6.6.
-        '';
-        default = null;
-        type = types.nullOr (types.listOf types.str);
-      };
-
-      username = mkOption {
-        description = lib.mdDoc "Username for elasticsearch basic auth.";
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      password = mkOption {
-        description = lib.mdDoc "Password for elasticsearch basic auth.";
-        default = null;
-        type = types.nullOr types.str;
-      };
-
-      ca = mkOption {
-        description = lib.mdDoc ''
-          CA file to auth against elasticsearch.
-
-          It's recommended to use the {option}`certificateAuthorities` option
-          when using kibana-5.4 or newer.
-        '';
-        default = null;
-        type = types.nullOr types.path;
-      };
-
-      certificateAuthorities = mkOption {
-        description = lib.mdDoc ''
-          CA files to auth against elasticsearch.
-
-          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 is defined.
-        '';
-        default = lib.optional (cfg.elasticsearch.ca != null) ca;
-        defaultText = literalExpression ''
-          lib.optional (config.${opt.elasticsearch.ca} != null) ca
-        '';
-        type = types.listOf types.path;
-      };
-
-      cert = mkOption {
-        description = lib.mdDoc "Certificate file to auth against elasticsearch.";
-        default = null;
-        type = types.nullOr types.path;
-      };
-
-      key = mkOption {
-        description = lib.mdDoc "Key file to auth against elasticsearch.";
-        default = null;
-        type = types.nullOr types.path;
-      };
-    };
-
-    package = mkOption {
-      description = lib.mdDoc "Kibana package to use";
-      default = pkgs.kibana;
-      defaultText = literalExpression "pkgs.kibana";
-      type = types.package;
-    };
-
-    dataDir = mkOption {
-      description = lib.mdDoc "Kibana data directory";
-      default = "/var/lib/kibana";
-      type = types.path;
-    };
-
-    extraConf = mkOption {
-      description = lib.mdDoc "Kibana extra configuration";
-      default = {};
-      type = types.attrs;
-    };
-  };
-
-  config = mkIf (cfg.enable) {
-    assertions = [
-      {
-        assertion = ge7 -> cfg.elasticsearch.url == null;
-        message =
-          "The option services.kibana.elasticsearch.url has been removed when using kibana >= 7.0.0. " +
-          "Please use option services.kibana.elasticsearch.hosts instead.";
-      }
-      {
-        assertion = lt6_6 -> cfg.elasticsearch.hosts == null;
-        message =
-          "The option services.kibana.elasticsearch.hosts is only valid for kibana >= 6.6.";
-      }
-    ];
-    systemd.services.kibana = {
-      description = "Kibana Service";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "elasticsearch.service" ];
-      environment = { BABEL_CACHE_PATH = "${cfg.dataDir}/.babelcache.json"; };
-      serviceConfig = {
-        ExecStart =
-          "${cfg.package}/bin/kibana" +
-          " --config ${cfgFile}" +
-          " --path.data ${cfg.dataDir}";
-        User = "kibana";
-        WorkingDirectory = cfg.dataDir;
-      };
-    };
-
-    environment.systemPackages = [ cfg.package ];
-
-    users.users.kibana = {
-      isSystemUser = true;
-      description = "Kibana service user";
-      home = cfg.dataDir;
-      createHome = true;
-      group = "kibana";
-    };
-    users.groups.kibana = {};
-  };
-}
diff --git a/nixos/modules/services/search/meilisearch.nix b/nixos/modules/services/search/meilisearch.nix
index 7c9fa62ae9542..4183847d1be31 100644
--- a/nixos/modules/services/search/meilisearch.nix
+++ b/nixos/modules/services/search/meilisearch.nix
@@ -16,11 +16,10 @@ in
   options.services.meilisearch = {
     enable = mkEnableOption (lib.mdDoc "MeiliSearch - a RESTful search API");
 
-    package = mkOption {
-      description = lib.mdDoc "The package to use for meilisearch. Use this if you require specific features to be enabled. The default package has no features.";
-      default = pkgs.meilisearch;
-      defaultText = lib.literalExpression "pkgs.meilisearch";
-      type = types.package;
+    package = mkPackageOption pkgs "meilisearch" {
+      extraDescription = ''
+        Use this if you require specific features to be enabled. The default package has no features.
+      '';
     };
 
     listenAddress = mkOption {
diff --git a/nixos/modules/services/search/opensearch.nix b/nixos/modules/services/search/opensearch.nix
index 9a50e79631380..3c054b6d7caa1 100644
--- a/nixos/modules/services/search/opensearch.nix
+++ b/nixos/modules/services/search/opensearch.nix
@@ -25,7 +25,7 @@ in
   options.services.opensearch = {
     enable = mkEnableOption (lib.mdDoc "OpenSearch");
 
-    package = lib.mkPackageOptionMD pkgs "OpenSearch" {
+    package = lib.mkPackageOption pkgs "OpenSearch" {
       default = [ "opensearch" ];
     };
 
@@ -72,6 +72,18 @@ in
             The port to listen on for transport traffic.
           '';
         };
+
+        options."plugins.security.disabled" = lib.mkOption {
+          type = lib.types.bool;
+          default = true;
+          description = lib.mdDoc ''
+            Whether to enable the security plugin,
+            `plugins.security.ssl.transport.keystore_filepath` or
+            `plugins.security.ssl.transport.server.pemcert_filepath` and
+            `plugins.security.ssl.transport.client.pemcert_filepath`
+            must be set for this plugin to be enabled.
+          '';
+        };
       };
 
       default = {};
@@ -186,6 +198,13 @@ in
               shopt -s inherit_errexit
 
               # Install plugins
+
+              # remove plugins directory if it is empty.
+              if [[ -d ${cfg.dataDir}/plugins && -z "$(ls -A ${cfg.dataDir}/plugins)" ]]; then
+                rm -r "${cfg.dataDir}/plugins"
+              fi
+
+              ln -sfT "${cfg.package}/plugins" "${cfg.dataDir}/plugins"
               ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
               ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
 
diff --git a/nixos/modules/services/search/sonic-server.nix b/nixos/modules/services/search/sonic-server.nix
new file mode 100644
index 0000000000000..59d96ae6b05a1
--- /dev/null
+++ b/nixos/modules/services/search/sonic-server.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.sonic-server;
+
+  settingsFormat = pkgs.formats.toml { };
+  configFile = settingsFormat.generate "sonic-server-config.toml" cfg.settings;
+
+in {
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  options = {
+    services.sonic-server = {
+      enable = lib.mkEnableOption (lib.mdDoc "Sonic Search Index");
+
+      package = lib.mkPackageOption pkgs "sonic-server" { };
+
+      settings = lib.mkOption {
+        type = lib.types.submodule { freeformType = settingsFormat.type; };
+        default = {
+          store.kv.path = "/var/lib/sonic/kv";
+          store.fst.path = "/var/lib/sonic/fst";
+        };
+        example = {
+          server.log_level = "debug";
+          channel.inet = "[::1]:1491";
+        };
+        description = lib.mdDoc ''
+          Sonic Server configuration options.
+
+          Refer to
+          <https://github.com/valeriansaliou/sonic/blob/master/CONFIGURATION.md>
+          for a full list of available options.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.sonic-server.settings = lib.mapAttrs (name: lib.mkDefault) {
+      server = {};
+      channel.search = {};
+      store = {
+        kv = {
+          path = "/var/lib/sonic/kv";
+          database = {};
+          pool = {};
+        };
+        fst = {
+          path = "/var/lib/sonic/fst";
+          graph = {};
+          pool = {};
+        };
+      };
+    };
+
+    systemd.services.sonic-server = {
+      description = "Sonic Search Index";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+
+        ExecStart = "${lib.getExe cfg.package} -c ${configFile}";
+        DynamicUser = true;
+        Group = "sonic";
+        LimitNOFILE = "infinity";
+        Restart = "on-failure";
+        StateDirectory = "sonic";
+        StateDirectoryMode = "750";
+        User = "sonic";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/search/typesense.nix b/nixos/modules/services/search/typesense.nix
index 856c3cad22df0..c158d04fea238 100644
--- a/nixos/modules/services/search/typesense.nix
+++ b/nixos/modules/services/search/typesense.nix
@@ -83,12 +83,12 @@ in {
         Group = "typesense";
 
         StateDirectory = "typesense";
-        StateDirectoryMode = "0700";
+        StateDirectoryMode = "0750";
 
         # Hardening
         CapabilityBoundingSet = "";
         LockPersonality = true;
-        MemoryDenyWriteExecute = true;
+        # MemoryDenyWriteExecute = true; needed since 0.25.1
         NoNewPrivileges = true;
         PrivateUsers = true;
         PrivateTmp = true;
diff --git a/nixos/modules/services/security/authelia.nix b/nixos/modules/services/security/authelia.nix
index cc55260e20f83..614b3b1e22b28 100644
--- a/nixos/modules/services/security/authelia.nix
+++ b/nixos/modules/services/security/authelia.nix
@@ -24,12 +24,7 @@ let
         '';
       };
 
-      package = mkOption {
-        default = pkgs.authelia;
-        type = types.package;
-        defaultText = literalExpression "pkgs.authelia";
-        description = mdDoc "Authelia derivation to use.";
-      };
+      package = mkPackageOption pkgs "authelia" { };
 
       user = mkOption {
         default = "authelia-${name}";
diff --git a/nixos/modules/services/security/bitwarden-directory-connector-cli.nix b/nixos/modules/services/security/bitwarden-directory-connector-cli.nix
new file mode 100644
index 0000000000000..18c02e22fd7e6
--- /dev/null
+++ b/nixos/modules/services/security/bitwarden-directory-connector-cli.nix
@@ -0,0 +1,323 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.services.bitwarden-directory-connector-cli;
+in {
+  options.services.bitwarden-directory-connector-cli = {
+    enable = mkEnableOption "Bitwarden Directory Connector";
+
+    package = mkPackageOption pkgs "bitwarden-directory-connector-cli" {};
+
+    domain = mkOption {
+      type = types.str;
+      description = lib.mdDoc "The domain the Bitwarden/Vaultwarden is accessible on.";
+      example = "https://vaultwarden.example.com";
+    };
+
+    user = mkOption {
+      type = types.str;
+      description = lib.mdDoc "User to run the program.";
+      default = "bwdc";
+    };
+
+    interval = mkOption {
+      type = types.str;
+      default = "*:0,15,30,45";
+      description = lib.mdDoc "The interval when to run the connector. This uses systemd's OnCalendar syntax.";
+    };
+
+    ldap = mkOption {
+      description = lib.mdDoc ''
+        Options to configure the LDAP connection.
+        If you used the desktop application to test the configuration you can find the settings by searching for `ldap` in `~/.config/Bitwarden\ Directory\ Connector/data.json`.
+      '';
+      default = {};
+      type = types.submodule ({
+        config,
+        options,
+        ...
+      }: {
+        freeformType = types.attrsOf (pkgs.formats.json {}).type;
+
+        config.finalJSON = builtins.toJSON (removeAttrs config (filter (x: x == "finalJSON" || ! options.${x}.isDefined or false) (attrNames options)));
+
+        options = {
+          finalJSON = mkOption {
+            type = (pkgs.formats.json {}).type;
+            internal = true;
+            readOnly = true;
+            visible = false;
+          };
+
+          ssl = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Whether to use TLS.";
+          };
+          startTls = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Whether to use STARTTLS.";
+          };
+
+          hostname = mkOption {
+            type = types.str;
+            description = lib.mdDoc "The host the LDAP is accessible on.";
+            example = "ldap.example.com";
+          };
+
+          port = mkOption {
+            type = types.port;
+            default = 389;
+            description = lib.mdDoc "Port LDAP is accessible on.";
+          };
+
+          ad = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Whether the LDAP Server is an Active Directory.";
+          };
+
+          pagedSearch = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Whether the LDAP server paginates search results.";
+          };
+
+          rootPath = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Root path for LDAP.";
+            example = "dc=example,dc=com";
+          };
+
+          username = mkOption {
+            type = types.str;
+            description = lib.mdDoc "The user to authenticate as.";
+            example = "cn=admin,dc=example,dc=com";
+          };
+        };
+      });
+    };
+
+    sync = mkOption {
+      description = lib.mdDoc ''
+        Options to configure what gets synced.
+        If you used the desktop application to test the configuration you can find the settings by searching for `sync` in `~/.config/Bitwarden\ Directory\ Connector/data.json`.
+      '';
+      default = {};
+      type = types.submodule ({
+        config,
+        options,
+        ...
+      }: {
+        freeformType = types.attrsOf (pkgs.formats.json {}).type;
+
+        config.finalJSON = builtins.toJSON (removeAttrs config (filter (x: x == "finalJSON" || ! options.${x}.isDefined or false) (attrNames options)));
+
+        options = {
+          finalJSON = mkOption {
+            type = (pkgs.formats.json {}).type;
+            internal = true;
+            readOnly = true;
+            visible = false;
+          };
+
+          removeDisabled = mkOption {
+            type = types.bool;
+            default = true;
+            description = lib.mdDoc "Remove users from bitwarden groups if no longer in the ldap group.";
+          };
+
+          overwriteExisting = mkOption {
+            type = types.bool;
+            default = false;
+            description =
+              lib.mdDoc "Remove and re-add users/groups, See https://bitwarden.com/help/user-group-filters/#overwriting-syncs for more details.";
+          };
+
+          largeImport = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Enable if you are syncing more than 2000 users/groups.";
+          };
+
+          memberAttribute = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Attribute that lists members in a LDAP group.";
+            example = "uniqueMember";
+          };
+
+          creationDateAttribute = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Attribute that lists a user's creation date.";
+            example = "whenCreated";
+          };
+
+          useEmailPrefixSuffix = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "If a user has no email address, combine a username prefix with a suffix value to form an email.";
+          };
+          emailPrefixAttribute = mkOption {
+            type = types.str;
+            description = lib.mdDoc "The attribute that contains the users username.";
+            example = "accountName";
+          };
+          emailSuffix = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Suffix for the email, normally @example.com.";
+            example = "@example.com";
+          };
+
+          users = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Sync users.";
+          };
+          userPath = mkOption {
+            type = types.str;
+            description = lib.mdDoc "User directory, relative to root.";
+            default = "ou=users";
+          };
+          userObjectClass = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Class that users must have.";
+            default = "inetOrgPerson";
+          };
+          userEmailAttribute = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Attribute for a users email.";
+            default = "mail";
+          };
+          userFilter = mkOption {
+            type = types.str;
+            description = lib.mdDoc "LDAP filter for users.";
+            example = "(memberOf=cn=sales,ou=groups,dc=example,dc=com)";
+            default = "";
+          };
+
+          groups = mkOption {
+            type = types.bool;
+            default = false;
+            description = lib.mdDoc "Whether to sync ldap groups into BitWarden.";
+          };
+          groupPath = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Group directory, relative to root.";
+            default = "ou=groups";
+          };
+          groupObjectClass = mkOption {
+            type = types.str;
+            description = lib.mdDoc "A class that groups will have.";
+            default = "groupOfNames";
+          };
+          groupNameAttribute = mkOption {
+            type = types.str;
+            description = lib.mdDoc "Attribute for a name of group.";
+            default = "cn";
+          };
+          groupFilter = mkOption {
+            type = types.str;
+            description = lib.mdDoc "LDAP filter for groups.";
+            example = "(cn=sales)";
+            default = "";
+          };
+        };
+      });
+    };
+
+    secrets = {
+      ldap = mkOption {
+        type = types.str;
+        description = "Path to file that contains LDAP password for user in {option}`ldap.username";
+      };
+
+      bitwarden = {
+        client_path_id = mkOption {
+          type = types.str;
+          description = "Path to file that contains Client ID.";
+        };
+        client_path_secret = mkOption {
+          type = types.str;
+          description = "Path to file that contains Client Secret.";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups."${cfg.user}" = {};
+    users.users."${cfg.user}" = {
+      isSystemUser = true;
+      group = cfg.user;
+    };
+
+    systemd = {
+      timers.bitwarden-directory-connector-cli = {
+        description = "Sync timer for Bitwarden Directory Connector";
+        wantedBy = ["timers.target"];
+        after = ["network-online.target"];
+        timerConfig = {
+          OnCalendar = cfg.interval;
+          Unit = "bitwarden-directory-connector-cli.service";
+          Persistent = true;
+        };
+      };
+
+      services.bitwarden-directory-connector-cli = {
+        description = "Main process for Bitwarden Directory Connector";
+        path = [pkgs.jq];
+
+        environment = {
+          BITWARDENCLI_CONNECTOR_APPDATA_DIR = "/tmp";
+          BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS = "true";
+        };
+
+        serviceConfig = {
+          Type = "oneshot";
+          User = "${cfg.user}";
+          PrivateTmp = true;
+          preStart = ''
+            set -eo pipefail
+
+            # create the config file
+            ${lib.getExe cfg.package} data-file
+            touch /tmp/data.json.tmp
+            chmod 600 /tmp/data.json{,.tmp}
+
+            ${lib.getExe cfg.package} config server ${cfg.domain}
+
+            # now login to set credentials
+            export BW_CLIENTID="$(< ${escapeShellArg cfg.secrets.bitwarden.client_path_id})"
+            export BW_CLIENTSECRET="$(< ${escapeShellArg cfg.secrets.bitwarden.client_path_secret})"
+            ${lib.getExe cfg.package} login
+
+            jq '.authenticatedAccounts[0] as $account
+              | .[$account].directoryConfigurations.ldap |= $ldap_data
+              | .[$account].directorySettings.organizationId |= $orgID
+              | .[$account].directorySettings.sync |= $sync_data' \
+              --argjson ldap_data ${escapeShellArg cfg.ldap.finalJSON} \
+              --arg orgID "''${BW_CLIENTID//organization.}" \
+              --argjson sync_data ${escapeShellArg cfg.sync.finalJSON} \
+              /tmp/data.json \
+              > /tmp/data.json.tmp
+
+            mv -f /tmp/data.json.tmp /tmp/data.json
+
+            # final config
+            ${lib.getExe cfg.package} config directory 0
+            ${lib.getExe cfg.package} config ldap.password --secretfile ${cfg.secrets.ldap}
+          '';
+
+          ExecStart = "${lib.getExe cfg.package} sync";
+        };
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [Silver-Golden];
+}
diff --git a/nixos/modules/services/security/certmgr.nix b/nixos/modules/services/security/certmgr.nix
index ca4cf5084722e..02cb7afe87bad 100644
--- a/nixos/modules/services/security/certmgr.nix
+++ b/nixos/modules/services/security/certmgr.nix
@@ -37,12 +37,7 @@ in
   options.services.certmgr = {
     enable = mkEnableOption (lib.mdDoc "certmgr");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.certmgr;
-      defaultText = literalExpression "pkgs.certmgr";
-      description = lib.mdDoc "Which certmgr package to use in the service.";
-    };
+    package = mkPackageOption pkgs "certmgr" { };
 
     defaultRemote = mkOption {
       type = types.str;
@@ -187,6 +182,7 @@ in
     systemd.services.certmgr = {
       description = "certmgr";
       path = mkIf (cfg.svcManager == "command") [ pkgs.bash ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       inherit preStart;
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index 34897a9ac7db8..4480c0cae60c9 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -3,7 +3,6 @@ with lib;
 let
   clamavUser = "clamav";
   stateDir = "/var/lib/clamav";
-  runDir = "/run/clamav";
   clamavGroup = clamavUser;
   cfg = config.services.clamav;
   pkg = pkgs.clamav;
@@ -15,6 +14,9 @@ let
 
   clamdConfigFile = pkgs.writeText "clamd.conf" (toKeyValue cfg.daemon.settings);
   freshclamConfigFile = pkgs.writeText "freshclam.conf" (toKeyValue cfg.updater.settings);
+  fangfrischConfigFile = pkgs.writeText "fangfrisch.conf" ''
+    ${lib.generators.toINI {} cfg.fangfrisch.settings}
+  '';
 in
 {
   imports = [
@@ -66,6 +68,59 @@ in
           '';
         };
       };
+      fangfrisch = {
+        enable = mkEnableOption (lib.mdDoc "ClamAV fangfrisch updater");
+
+        interval = mkOption {
+          type = types.str;
+          default = "hourly";
+          description = lib.mdDoc ''
+            How often freshclam is invoked. See systemd.time(7) for more
+            information about the format.
+          '';
+        };
+
+        settings = mkOption {
+          type = lib.types.submodule {
+            freeformType = with types; attrsOf (attrsOf (oneOf [ str int bool ]));
+          };
+          default = { };
+          example = {
+            securiteinfo = {
+              enabled = "yes";
+              customer_id = "your customer_id";
+            };
+          };
+          description = lib.mdDoc ''
+            fangfrisch configuration. Refer to <https://rseichter.github.io/fangfrisch/#_configuration>,
+            for details on supported values.
+            Note that by default urlhaus and sanesecurity are enabled.
+          '';
+        };
+      };
+
+      scanner = {
+        enable = mkEnableOption (lib.mdDoc "ClamAV scanner");
+
+        interval = mkOption {
+          type = types.str;
+          default = "*-*-* 04:00:00";
+          description = lib.mdDoc ''
+            How often clamdscan is invoked. See systemd.time(7) for more
+            information about the format.
+            By default this runs using 10 cores at most, be sure to run it at a time of low traffic.
+          '';
+        };
+
+        scanDirectories = mkOption {
+          type = with types; listOf str;
+          default = [ "/home" "/var/lib" "/tmp" "/etc" "/var/tmp" ];
+          description = lib.mdDoc ''
+            List of directories to scan.
+            The default includes everything I could think of that is valid for nixos. Feel free to contribute a PR to add to the default if you see something missing.
+          '';
+        };
+      };
     };
   };
 
@@ -84,9 +139,8 @@ in
 
     services.clamav.daemon.settings = {
       DatabaseDirectory = stateDir;
-      LocalSocket = "${runDir}/clamd.ctl";
-      PidFile = "${runDir}/clamd.pid";
-      TemporaryDirectory = "/tmp";
+      LocalSocket = "/run/clamav/clamd.ctl";
+      PidFile = "/run/clamav/clamd.pid";
       User = "clamav";
       Foreground = true;
     };
@@ -98,23 +152,32 @@ in
       DatabaseMirror = [ "database.clamav.net" ];
     };
 
+    services.clamav.fangfrisch.settings = {
+      DEFAULT.db_url = mkDefault "sqlite:////var/lib/clamav/fangfrisch_db.sqlite";
+      DEFAULT.local_directory = mkDefault stateDir;
+      DEFAULT.log_level = mkDefault "INFO";
+      urlhaus.enabled = mkDefault "yes";
+      urlhaus.max_size = mkDefault "2MB";
+      sanesecurity.enabled = mkDefault "yes";
+    };
+
     environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
     environment.etc."clamav/clamd.conf".source = clamdConfigFile;
 
     systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
       description = "ClamAV daemon (clamd)";
-      after = optional cfg.updater.enable "clamav-freshclam.service";
+      after = optionals cfg.updater.enable [ "clamav-freshclam.service" ];
+      wants = optionals cfg.updater.enable [ "clamav-freshclam.service" ];
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ clamdConfigFile ];
 
-      preStart = ''
-        mkdir -m 0755 -p ${runDir}
-        chown ${clamavUser}:${clamavGroup} ${runDir}
-      '';
-
       serviceConfig = {
         ExecStart = "${pkg}/bin/clamd";
         ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+        User = clamavUser;
+        Group = clamavGroup;
+        StateDirectory = "clamav";
+        RuntimeDirectory = "clamav";
         PrivateTmp = "yes";
         PrivateDevices = "yes";
         PrivateNetwork = "yes";
@@ -133,19 +196,86 @@ in
     systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
       description = "ClamAV virus database updater (freshclam)";
       restartTriggers = [ freshclamConfigFile ];
+      requires = [ "network-online.target" ];
       after = [ "network-online.target" ];
-      preStart = ''
-        mkdir -m 0755 -p ${stateDir}
-        chown ${clamavUser}:${clamavGroup} ${stateDir}
-      '';
 
       serviceConfig = {
         Type = "oneshot";
         ExecStart = "${pkg}/bin/freshclam";
         SuccessExitStatus = "1"; # if databases are up to date
+        StateDirectory = "clamav";
+        User = clamavUser;
+        Group = clamavGroup;
         PrivateTmp = "yes";
         PrivateDevices = "yes";
       };
     };
+
+    systemd.services.clamav-fangfrisch-init = mkIf cfg.fangfrisch.enable {
+      wantedBy = [ "multi-user.target" ];
+      # if the sqlite file can be found assume the database has already been initialised
+      script = ''
+        db_url="${cfg.fangfrisch.settings.DEFAULT.db_url}"
+        db_path="''${db_url#sqlite:///}"
+
+        if [ ! -f "$db_path" ]; then
+          ${pkgs.fangfrisch}/bin/fangfrisch --conf ${fangfrischConfigFile} initdb
+        fi
+      '';
+      serviceConfig = {
+        Type = "oneshot";
+        StateDirectory = "clamav";
+        User = clamavUser;
+        Group = clamavGroup;
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+      };
+    };
+
+    systemd.timers.clamav-fangfrisch = mkIf cfg.fangfrisch.enable {
+      description = "Timer for ClamAV virus database updater (fangfrisch)";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.fangfrisch.interval;
+        Unit = "clamav-fangfrisch.service";
+      };
+    };
+
+    systemd.services.clamav-fangfrisch = mkIf cfg.fangfrisch.enable {
+      description = "ClamAV virus database updater (fangfrisch)";
+      restartTriggers = [ fangfrischConfigFile ];
+      requires = [ "network-online.target" ];
+      after = [ "network-online.target" "clamav-fangfrisch-init.service" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.fangfrisch}/bin/fangfrisch --conf ${fangfrischConfigFile} refresh";
+        StateDirectory = "clamav";
+        User = clamavUser;
+        Group = clamavGroup;
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+      };
+    };
+
+    systemd.timers.clamdscan = mkIf cfg.scanner.enable {
+      description = "Timer for ClamAV virus scanner";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.scanner.interval;
+        Unit = "clamdscan.service";
+      };
+    };
+
+    systemd.services.clamdscan = mkIf cfg.scanner.enable {
+      description = "ClamAV virus scanner";
+      after = optionals cfg.updater.enable [ "clamav-freshclam.service" ];
+      wants = optionals cfg.updater.enable [ "clamav-freshclam.service" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkg}/bin/clamdscan --multiscan --fdpass --infected --allmatch ${lib.concatStringsSep " " cfg.scanner.scanDirectories}";
+      };
+    };
   };
 }
diff --git a/nixos/modules/services/security/esdm.nix b/nixos/modules/services/security/esdm.nix
index 2b246fff7e960..134b4be1a94c8 100644
--- a/nixos/modules/services/security/esdm.nix
+++ b/nixos/modules/services/security/esdm.nix
@@ -6,7 +6,7 @@ in
 {
   options.services.esdm = {
     enable = lib.mkEnableOption (lib.mdDoc "ESDM service configuration");
-    package = lib.mkPackageOptionMD pkgs "esdm" { };
+    package = lib.mkPackageOption pkgs "esdm" { };
     serverEnable = lib.mkOption {
       type = lib.types.bool;
       default = true;
diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix
index 9393fa7512886..59b9ea70209d4 100644
--- a/nixos/modules/services/security/fail2ban.nix
+++ b/nixos/modules/services/security/fail2ban.nix
@@ -77,12 +77,8 @@ in
         '';
       };
 
-      package = mkOption {
-        default = pkgs.fail2ban;
-        defaultText = literalExpression "pkgs.fail2ban";
-        type = types.package;
-        example = literalExpression "pkgs.fail2ban_0_11";
-        description = lib.mdDoc "The fail2ban package to use for running the fail2ban service.";
+      package = mkPackageOption pkgs "fail2ban" {
+        example = "fail2ban_0_11";
       };
 
       packageFirewall = mkOption {
@@ -103,9 +99,9 @@ in
       };
 
       bantime = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        example = "10m";
+        default = "10m";
+        type = types.str;
+        example = "1h";
         description = lib.mdDoc "Number of seconds that a host is banned.";
       };
 
@@ -128,8 +124,8 @@ in
       };
 
       banaction-allports = mkOption {
-        default = if config.networking.nftables.enable then "nftables-allport" else "iptables-allport";
-        defaultText = literalExpression ''if config.networking.nftables.enable then "nftables-allport" else "iptables-allport"'';
+        default = if config.networking.nftables.enable then "nftables-allports" else "iptables-allports";
+        defaultText = literalExpression ''if config.networking.nftables.enable then "nftables-allports" else "iptables-allports"'';
         type = types.str;
         description = lib.mdDoc ''
           Default banning action (e.g. iptables, iptables-new, iptables-multiport,
@@ -393,7 +389,7 @@ in
           )
         ) // {
           # Miscellaneous options
-          inherit (cfg) banaction maxretry;
+          inherit (cfg) banaction maxretry bantime;
           ignoreip = ''127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}'';
           backend = "systemd";
           # Actions
diff --git a/nixos/modules/services/security/haka.nix b/nixos/modules/services/security/haka.nix
index c93638f44d60e..dda039857401f 100644
--- a/nixos/modules/services/security/haka.nix
+++ b/nixos/modules/services/security/haka.nix
@@ -57,14 +57,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "Haka");
 
-      package = mkOption {
-        default = pkgs.haka;
-        defaultText = literalExpression "pkgs.haka";
-        type = types.package;
-        description = lib.mdDoc ''
-          Which Haka derivation to use.
-        '';
-      };
+      package = mkPackageOption pkgs "haka" { };
 
       configFile = mkOption {
         default = "empty.lua";
diff --git a/nixos/modules/services/security/hockeypuck.nix b/nixos/modules/services/security/hockeypuck.nix
index 127134bc5dba5..56c13d791920c 100644
--- a/nixos/modules/services/security/hockeypuck.nix
+++ b/nixos/modules/services/security/hockeypuck.nix
@@ -55,7 +55,7 @@ in {
             ensureDatabases = [ "hockeypuck" ];
             ensureUsers = [{
               name = "hockeypuck";
-              ensurePermissions."DATABASE hockeypuck" = "ALL PRIVILEGES";
+              ensureDBOwnership = true;
             }];
           };
         ```
diff --git a/nixos/modules/services/security/jitterentropy-rngd.nix b/nixos/modules/services/security/jitterentropy-rngd.nix
new file mode 100644
index 0000000000000..289d2f7a9839d
--- /dev/null
+++ b/nixos/modules/services/security/jitterentropy-rngd.nix
@@ -0,0 +1,18 @@
+{ lib, config, pkgs, ... }:
+let
+  cfg = config.services.jitterentropy-rngd;
+in
+{
+  options.services.jitterentropy-rngd = {
+    enable =
+      lib.mkEnableOption (lib.mdDoc "jitterentropy-rngd service configuration");
+    package = lib.mkPackageOption pkgs "jitterentropy-rngd" { };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.packages = [ cfg.package ];
+    systemd.services."jitterentropy".wantedBy = [ "basic.target" ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ thillux ];
+}
diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix
index 6f4d1dc382ab8..c8d8f69729e9b 100644
--- a/nixos/modules/services/security/kanidm.nix
+++ b/nixos/modules/services/security/kanidm.nix
@@ -69,7 +69,7 @@ in
     enableServer = lib.mkEnableOption (lib.mdDoc "the Kanidm server");
     enablePam = lib.mkEnableOption (lib.mdDoc "the Kanidm PAM and NSS integration");
 
-    package = lib.mkPackageOptionMD pkgs "kanidm" {};
+    package = lib.mkPackageOption pkgs "kanidm" {};
 
     serverSettings = lib.mkOption {
       type = lib.types.submodule {
diff --git a/nixos/modules/services/security/munge.nix b/nixos/modules/services/security/munge.nix
index 4d6fe33f697b8..9d306c205f946 100644
--- a/nixos/modules/services/security/munge.nix
+++ b/nixos/modules/services/security/munge.nix
@@ -45,19 +45,25 @@ in
 
     systemd.services.munged = {
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
+      wants = [
+        "network-online.target"
+        "time-sync.target"
+      ];
+      after = [
+        "network-online.target"
+        "time-sync.target"
+      ];
 
       path = [ pkgs.munge pkgs.coreutils ];
 
       serviceConfig = {
         ExecStartPre = "+${pkgs.coreutils}/bin/chmod 0400 ${cfg.password}";
-        ExecStart = "${pkgs.munge}/bin/munged --syslog --key-file ${cfg.password}";
-        PIDFile = "/run/munge/munged.pid";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStart = "${pkgs.munge}/bin/munged --foreground --key-file ${cfg.password}";
         User = "munge";
         Group = "munge";
         StateDirectory = "munge";
         StateDirectoryMode = "0711";
+        Restart = "on-failure";
         RuntimeDirectory = "munge";
       };
 
diff --git a/nixos/modules/services/security/nginx-sso.nix b/nixos/modules/services/security/nginx-sso.nix
index 971f22ed3476e..dd32b8356cbba 100644
--- a/nixos/modules/services/security/nginx-sso.nix
+++ b/nixos/modules/services/security/nginx-sso.nix
@@ -10,14 +10,7 @@ in {
   options.services.nginx.sso = {
     enable = mkEnableOption (lib.mdDoc "nginx-sso service");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.nginx-sso;
-      defaultText = literalExpression "pkgs.nginx-sso";
-      description = lib.mdDoc ''
-        The nginx-sso package that should be used.
-      '';
-    };
+    package = mkPackageOption pkgs "nginx-sso" { };
 
     configuration = mkOption {
       type = types.attrsOf types.unspecified;
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 718c3d2498eac..d1dc37d549d2d 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -87,14 +87,7 @@ in
   options.services.oauth2_proxy = {
     enable = mkEnableOption (lib.mdDoc "oauth2_proxy");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.oauth2-proxy;
-      defaultText = literalExpression "pkgs.oauth2-proxy";
-      description = lib.mdDoc ''
-        The package that provides oauth2-proxy.
-      '';
-    };
+    package = mkPackageOption pkgs "oauth2-proxy" { };
 
     ##############################################
     # PROVIDER configuration
@@ -579,6 +572,7 @@ in
       description = "OAuth2 Proxy";
       path = [ cfg.package ];
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
 
       serviceConfig = {
diff --git a/nixos/modules/services/security/opensnitch.nix b/nixos/modules/services/security/opensnitch.nix
index 013aeb16756cd..97ac3a72804c2 100644
--- a/nixos/modules/services/security/opensnitch.nix
+++ b/nixos/modules/services/security/opensnitch.nix
@@ -172,7 +172,7 @@ in {
         ln -sf '${file}' "${local}"
       '') rules}
 
-      if [ ! -f /etc/opensnitch-system-fw.json ]; then
+      if [ ! -f /etc/opensnitchd/system-fw.json ]; then
         cp "${pkgs.opensnitch}/etc/opensnitchd/system-fw.json" "/etc/opensnitchd/system-fw.json"
       fi
     '');
diff --git a/nixos/modules/services/security/pass-secret-service.nix b/nixos/modules/services/security/pass-secret-service.nix
index c3c70d97ff59b..f864f8a265956 100644
--- a/nixos/modules/services/security/pass-secret-service.nix
+++ b/nixos/modules/services/security/pass-secret-service.nix
@@ -9,12 +9,8 @@ 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 }";
+    package = mkPackageOption pkgs "pass-secret-service" {
+      example = "pass-secret-service.override { python3 = pkgs.python310 }";
     };
   };
 
diff --git a/nixos/modules/services/security/privacyidea.nix b/nixos/modules/services/security/privacyidea.nix
deleted file mode 100644
index 664335cb58e89..0000000000000
--- a/nixos/modules/services/security/privacyidea.nix
+++ /dev/null
@@ -1,458 +0,0 @@
-{ config, lib, options, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.privacyidea;
-  opt = options.services.privacyidea;
-
-  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; python3 = pkgs.python310; };
-  python = uwsgi.python3;
-  penv = python.withPackages (const [ pkgs.privacyidea ]);
-  logCfg = pkgs.writeText "privacyidea-log.cfg" ''
-    [formatters]
-    keys=detail
-
-    [handlers]
-    keys=stream
-
-    [formatter_detail]
-    class=privacyidea.lib.log.SecureFormatter
-    format=[%(asctime)s][%(process)d][%(thread)d][%(levelname)s][%(name)s:%(lineno)d] %(message)s
-
-    [handler_stream]
-    class=StreamHandler
-    level=NOTSET
-    formatter=detail
-    args=(sys.stdout,)
-
-    [loggers]
-    keys=root,privacyidea
-
-    [logger_privacyidea]
-    handlers=stream
-    qualname=privacyidea
-    level=INFO
-
-    [logger_root]
-    handlers=stream
-    level=ERROR
-  '';
-
-  piCfgFile = pkgs.writeText "privacyidea.cfg" ''
-    SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
-    SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2:///privacyidea'
-    SECRET_KEY = '${cfg.secretKey}'
-    PI_PEPPER = '${cfg.pepper}'
-    PI_ENCFILE = '${cfg.encFile}'
-    PI_AUDIT_KEY_PRIVATE = '${cfg.auditKeyPrivate}'
-    PI_AUDIT_KEY_PUBLIC = '${cfg.auditKeyPublic}'
-    PI_LOGCONFIG = '${logCfg}'
-    ${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 (lib.mdDoc "PrivacyIDEA");
-
-      environmentFile = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        example = "/root/privacyidea.env";
-        description = lib.mdDoc ''
-          File to load as environment file. Environment variables
-          from this file will be interpolated into the config file
-          using `envsubst` which is helpful for specifying
-          secrets:
-          ```
-          { services.privacyidea.secretKey = "$SECRET"; }
-          ```
-
-          The environment-file can now specify the actual secret key:
-          ```
-          SECRET=veryverytopsecret
-          ```
-        '';
-      };
-
-      stateDir = mkOption {
-        type = types.str;
-        default = "/var/lib/privacyidea";
-        description = lib.mdDoc ''
-          Directory where all PrivacyIDEA files will be placed by default.
-        '';
-      };
-
-      superuserRealm = mkOption {
-        type = types.listOf types.str;
-        default = [ "super" "administrators" ];
-        description = lib.mdDoc ''
-          The realm where users are allowed to login as administrators.
-        '';
-      };
-
-      secretKey = mkOption {
-        type = types.str;
-        example = "t0p s3cr3t";
-        description = lib.mdDoc ''
-          This is used to encrypt the auth_token.
-        '';
-      };
-
-      pepper = mkOption {
-        type = types.str;
-        example = "Never know...";
-        description = lib.mdDoc ''
-          This is used to encrypt the admin passwords.
-        '';
-      };
-
-      encFile = mkOption {
-        type = types.str;
-        default = "${cfg.stateDir}/enckey";
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/enckey"'';
-        description = lib.mdDoc ''
-          This is used to encrypt the token data and token passwords
-        '';
-      };
-
-      auditKeyPrivate = mkOption {
-        type = types.str;
-        default = "${cfg.stateDir}/private.pem";
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/private.pem"'';
-        description = lib.mdDoc ''
-          Private Key for signing the audit log.
-        '';
-      };
-
-      auditKeyPublic = mkOption {
-        type = types.str;
-        default = "${cfg.stateDir}/public.pem";
-        defaultText = literalExpression ''"''${config.${opt.stateDir}}/public.pem"'';
-        description = lib.mdDoc ''
-          Public key for checking signatures of the audit log.
-        '';
-      };
-
-      adminPasswordFile = mkOption {
-        type = types.path;
-        description = lib.mdDoc "File containing password for the admin user";
-      };
-
-      adminEmail = mkOption {
-        type = types.str;
-        example = "admin@example.com";
-        description = lib.mdDoc "Mail address for the admin user";
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = lib.mdDoc ''
-          Extra configuration options for pi.cfg.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        default = "privacyidea";
-        description = lib.mdDoc "User account under which PrivacyIDEA runs.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "privacyidea";
-        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 (lib.mdDoc "PrivacyIDEA LDAP Proxy");
-
-        configFile = mkOption {
-          type = types.nullOr types.path;
-          default = null;
-          description = lib.mdDoc ''
-            Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
-          '';
-        };
-
-        user = mkOption {
-          type = types.str;
-          default = "pi-ldap-proxy";
-          description = lib.mdDoc "User account under which PrivacyIDEA LDAP proxy runs.";
-        };
-
-        group = mkOption {
-          type = types.str;
-          default = "pi-ldap-proxy";
-          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).
-          '';
-        };
-      };
-    };
-  };
-
-  config = mkMerge [
-
-    (mkIf cfg.enable {
-
-      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 = {
-            buffer-size = 8192;
-            plugins = [ "python3" ];
-            pythonpath = "${penv}/${uwsgi.python3.sitePackages}";
-            socket = "/run/privacyidea/socket";
-            uid = cfg.user;
-            gid = cfg.group;
-            chmod-socket = 770;
-            chown-socket = "${cfg.user}:nginx";
-            chdir = cfg.stateDir;
-            wsgi-file = "${penv}/etc/privacyidea/privacyideaapp.wsgi";
-            processes = 4;
-            harakiri = 60;
-            reload-mercy = 8;
-            stats = "/run/privacyidea/stats.socket";
-            max-requests = 2000;
-            limit-as = 1024;
-            reload-on-as = 512;
-            reload-on-rss = 256;
-            no-orphans = true;
-            vacuum = true;
-          };
-        });
-      in {
-        wantedBy = [ "multi-user.target" ];
-        after = [ "postgresql.service" ];
-        path = with pkgs; [ openssl ];
-        environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
-        preStart = let
-          pi-manage = "${config.security.sudo.package}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
-          pgsu = config.services.postgresql.superUser;
-          psql = config.services.postgresql.package;
-        in ''
-          mkdir -p ${cfg.stateDir} /run/privacyidea
-          chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea
-          umask 077
-          ${lib.getBin pkgs.envsubst}/bin/envsubst -o ${cfg.stateDir}/privacyidea.cfg \
-                                                   -i "${piCfgFile}"
-          chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/privacyidea.cfg
-          if ! test -e "${cfg.stateDir}/db-created"; then
-            ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
-            ${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
-            ${pi-manage} create_enckey
-            ${pi-manage} create_audit_keys
-            ${pi-manage} createdb
-            ${pi-manage} admin add admin -e ${cfg.adminEmail} -p "$(cat ${cfg.adminPasswordFile})"
-            ${pi-manage} db stamp head -d ${penv}/lib/privacyidea/migrations
-            touch "${cfg.stateDir}/db-created"
-            chmod g+r "${cfg.stateDir}/enckey" "${cfg.stateDir}/private.pem"
-          fi
-          ${pi-manage} db upgrade -d ${penv}/lib/privacyidea/migrations
-        '';
-        serviceConfig = {
-          Type = "notify";
-          ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}";
-          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
-          ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
-          NotifyAccess = "main";
-          KillSignal = "SIGQUIT";
-        };
-      };
-
-      users.users.privacyidea = mkIf (cfg.user == "privacyidea") {
-        group = cfg.group;
-        isSystemUser = true;
-      };
-
-      users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {};
-    })
-
-    (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 {
-        description = "privacyIDEA LDAP proxy";
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = {
-          User = cfg.ldap-proxy.user;
-          Group = cfg.ldap-proxy.group;
-          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 ${configPath}
-          '';
-          Restart = "always";
-        };
-      };
-
-      users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") {
-        group = cfg.ldap-proxy.group;
-        isSystemUser = true;
-      };
-
-      users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {};
-    })
-  ];
-
-}
diff --git a/nixos/modules/services/security/shibboleth-sp.nix b/nixos/modules/services/security/shibboleth-sp.nix
index e7897c3324cf6..975de1efa2f2a 100644
--- a/nixos/modules/services/security/shibboleth-sp.nix
+++ b/nixos/modules/services/security/shibboleth-sp.nix
@@ -1,44 +1,43 @@
-{pkgs, config, lib, ...}:
+{ config, lib, pkgs, ... }:
 
-with lib;
 let
   cfg = config.services.shibboleth-sp;
 in {
   options = {
     services.shibboleth-sp = {
-      enable = mkOption {
-        type = types.bool;
+      enable = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc "Whether to enable the shibboleth service";
       };
 
-      configFile = mkOption {
-        type = types.path;
-        example = literalExpression ''"''${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml"'';
+      configFile = lib.mkOption {
+        type = lib.types.path;
+        example = lib.literalExpression ''"''${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml"'';
         description = lib.mdDoc "Path to shibboleth config file";
       };
 
-      fastcgi.enable = mkOption {
-        type = types.bool;
+      fastcgi.enable = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc "Whether to include the shibauthorizer and shibresponder FastCGI processes";
       };
 
-      fastcgi.shibAuthorizerPort = mkOption {
-        type = types.int;
+      fastcgi.shibAuthorizerPort = lib.mkOption {
+        type = lib.types.int;
         default = 9100;
         description = lib.mdDoc "Port for shibauthorizer FastCGI process to bind to";
       };
 
-      fastcgi.shibResponderPort = mkOption {
-        type = types.int;
+      fastcgi.shibResponderPort = lib.mkOption {
+        type = lib.types.int;
         default = 9101;
         description = lib.mdDoc "Port for shibauthorizer FastCGI process to bind to";
       };
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     systemd.services.shibboleth-sp = {
       description = "Provides SSO and federation for web applications";
       after       = lib.optionals cfg.fastcgi.enable [ "shibresponder.service" "shibauthorizer.service" ];
@@ -48,7 +47,7 @@ in {
       };
     };
 
-    systemd.services.shibresponder = mkIf cfg.fastcgi.enable {
+    systemd.services.shibresponder = lib.mkIf cfg.fastcgi.enable {
       description = "Provides SSO through Shibboleth via FastCGI";
       after       = [ "network.target" ];
       wantedBy    = [ "multi-user.target" ];
@@ -59,7 +58,7 @@ in {
       };
     };
 
-    systemd.services.shibauthorizer = mkIf cfg.fastcgi.enable {
+    systemd.services.shibauthorizer = lib.mkIf cfg.fastcgi.enable {
       description = "Provides SSO through Shibboleth via FastCGI";
       after       = [ "network.target" ];
       wantedBy    = [ "multi-user.target" ];
@@ -71,5 +70,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ jammerful ];
+  meta.maintainers = with lib.maintainers; [ ];
 }
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 550b61916a223..7ac5ecec0d824 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -21,12 +21,7 @@ in {
         server. You need to create "''${dataDir}/dump/*.gpg" for the initial
         import'');
 
-      package = mkOption {
-        default = pkgs.sks;
-        defaultText = literalExpression "pkgs.sks";
-        type = types.package;
-        description = lib.mdDoc "Which SKS derivation to use.";
-      };
+      package = mkPackageOption pkgs "sks" { };
 
       dataDir = mkOption {
         type = types.path;
diff --git a/nixos/modules/services/security/tang.nix b/nixos/modules/services/security/tang.nix
new file mode 100644
index 0000000000000..9cb0a22fca427
--- /dev/null
+++ b/nixos/modules/services/security/tang.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.tang;
+in
+{
+  options.services.tang = {
+    enable = mkEnableOption "tang";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.tang;
+      defaultText = literalExpression "pkgs.tang";
+      description = mdDoc "The tang package to use.";
+    };
+
+    listenStream = mkOption {
+      type = with types; listOf str;
+      default = [ "7654" ];
+      example = [ "198.168.100.1:7654" "[2001:db8::1]:7654" "7654" ];
+      description = mdDoc ''
+        Addresses and/or ports on which tang should listen.
+        For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
+      '';
+    };
+
+    ipAddressAllow = mkOption {
+      example = [ "192.168.1.0/24" ];
+      type = types.listOf types.str;
+      description = ''
+        Whitelist a list of address prefixes.
+        Preferably, internal addresses should be used.
+      '';
+    };
+
+  };
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services."tangd@" = {
+      description = "Tang server";
+      path = [ cfg.package ];
+      serviceConfig = {
+        StandardInput = "socket";
+        StandardOutput = "socket";
+        StandardError = "journal";
+        DynamicUser = true;
+        StateDirectory = "tang";
+        RuntimeDirectory = "tang";
+        StateDirectoryMode = "700";
+        UMask = "0077";
+        CapabilityBoundingSet = [ "" ];
+        ExecStart = "${cfg.package}/libexec/tangd %S/tang";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        DeviceAllow = [ "/dev/stdin" ];
+        RestrictAddressFamilies = [ "AF_UNIX" ];
+        DevicePolicy = "strict";
+        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";
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+        IPAddressDeny = "any";
+        IPAddressAllow = cfg.ipAddressAllow;
+      };
+    };
+
+    systemd.sockets.tangd = {
+      description = "Tang server";
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream = cfg.listenStream;
+        Accept = "yes";
+        IPAddressDeny = "any";
+        IPAddressAllow = cfg.ipAddressAllow;
+      };
+    };
+  };
+  meta.maintainers = with lib.maintainers; [ jfroche julienmalka ];
+}
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index 9e786eb2bf06c..dea20dec1ab47 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -230,12 +230,7 @@ in
 
       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 = lib.mdDoc "Tor package to use.";
-      };
+      package = mkPackageOption pkgs "tor" { };
 
       enableGeoIP = mkEnableOption (lib.mdDoc ''use of GeoIP databases.
         Disabling this will disable by-country statistics for bridges and relays
@@ -859,7 +854,7 @@ in
           BridgeRelay = true;
           ExtORPort.port = mkDefault "auto";
           ServerTransportPlugin.transports = mkDefault ["obfs4"];
-          ServerTransportPlugin.exec = mkDefault "${pkgs.obfs4}/bin/obfs4proxy managed";
+          ServerTransportPlugin.exec = mkDefault "${lib.getExe pkgs.obfs4} managed";
         } // optionalAttrs (cfg.relay.role == "private-bridge") {
           ExtraInfoStatistics = false;
           PublishServerDescriptor = false;
diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix
index 9b158bb9d18c0..f167fbb2eca82 100644
--- a/nixos/modules/services/security/usbguard.nix
+++ b/nixos/modules/services/security/usbguard.nix
@@ -7,10 +7,8 @@ let
   # valid policy options
   policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
 
-  defaultRuleFile = "/var/lib/usbguard/rules.conf";
-
   # decide what file to use for rules
-  ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else defaultRuleFile;
+  ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else cfg.ruleFile;
 
   daemonConf = ''
     # generated by nixos/modules/services/security/usbguard.nix
@@ -41,16 +39,25 @@ in
     services.usbguard = {
       enable = mkEnableOption (lib.mdDoc "USBGuard daemon");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.usbguard;
-        defaultText = literalExpression "pkgs.usbguard";
-        description = lib.mdDoc ''
-          The usbguard package to use. If you do not need the Qt GUI, use
-          `pkgs.usbguard-nox` to save disk space.
+      package = mkPackageOption pkgs "usbguard" {
+        extraDescription = ''
+          If you do not need the Qt GUI, use `pkgs.usbguard-nox` to save disk space.
         '';
       };
 
+      ruleFile = mkOption {
+        type = types.nullOr types.path;
+        default = "/var/lib/usbguard/rules.conf";
+        example = "/run/secrets/usbguard-rules";
+        description = lib.mdDoc ''
+          This tells the USBGuard daemon which file to load as policy rule set.
+
+          The file can be changed manually or via the IPC interface assuming it has the right file permissions.
+
+          For more details see {manpage}`usbguard-rules.conf(5)`.
+        '';
+
+      };
       rules = mkOption {
         type = types.nullOr types.lines;
         default = null;
@@ -63,8 +70,7 @@ in
           be changed by the IPC interface.
 
           If you do not set this option, the USBGuard daemon will load
-          it's policy rule set from `${defaultRuleFile}`.
-          This file can be changed manually or via the IPC interface.
+          it's policy rule set from the option configured in `services.usbguard.ruleFile`.
 
           Running `usbguard generate-policy` as root will
           generate a config for your currently plugged in devices.
@@ -248,7 +254,6 @@ in
       '';
   };
   imports = [
-    (mkRemovedOptionModule [ "services" "usbguard" "ruleFile" ] "The usbguard module now uses ${defaultRuleFile} as ruleFile. Alternatively, use services.usbguard.rules to configure rules.")
     (mkRemovedOptionModule [ "services" "usbguard" "IPCAccessControlFiles" ] "The usbguard module now hardcodes IPCAccessControlFiles to /var/lib/usbguard/IPCAccessControl.d.")
     (mkRemovedOptionModule [ "services" "usbguard" "auditFilePath" ] "Removed usbguard module audit log files. Audit logs can be found in the systemd journal.")
     (mkRenamedOptionModule [ "services" "usbguard" "implictPolicyTarget" ] [ "services" "usbguard" "implicitPolicyTarget" ])
diff --git a/nixos/modules/services/security/vault-agent.nix b/nixos/modules/services/security/vault-agent.nix
index 17b8ff83592e1..f8c281442f5fe 100644
--- a/nixos/modules/services/security/vault-agent.nix
+++ b/nixos/modules/services/security/vault-agent.nix
@@ -14,7 +14,7 @@ let
       options = {
         enable = mkEnableOption (mdDoc "this ${flavour} instance") // { default = true; };
 
-        package = mkPackageOptionMD pkgs pkgName { };
+        package = mkPackageOption pkgs pkgName { };
 
         user = mkOption {
           type = types.str;
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index 18d981cdb0d29..31782073968fe 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -45,12 +45,7 @@ in
     services.vault = {
       enable = mkEnableOption (lib.mdDoc "Vault daemon");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.vault;
-        defaultText = literalExpression "pkgs.vault";
-        description = lib.mdDoc "This option specifies the vault package to use.";
-      };
+      package = mkPackageOption pkgs "vault" { };
 
       dev = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/security/vaultwarden/backup.sh b/nixos/modules/services/security/vaultwarden/backup.sh
index 2a3de0ab1deeb..7668da5bc88f3 100644
--- a/nixos/modules/services/security/vaultwarden/backup.sh
+++ b/nixos/modules/services/security/vaultwarden/backup.sh
@@ -1,8 +1,8 @@
 #!/usr/bin/env bash
 
 # Based on: https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault
-if ! mkdir -p "$BACKUP_FOLDER"; then
-  echo "Could not create backup folder '$BACKUP_FOLDER'" >&2
+if [ ! -d "$BACKUP_FOLDER" ]; then
+  echo "Backup folder '$BACKUP_FOLDER' does not exist" >&2
   exit 1
 fi
 
diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix
index d22e6b5b40cdc..470db735bf649 100644
--- a/nixos/modules/services/security/vaultwarden/default.nix
+++ b/nixos/modules/services/security/vaultwarden/default.nix
@@ -55,15 +55,14 @@ in {
       description = lib.mdDoc ''
         The directory under which vaultwarden will backup its persistent data.
       '';
+      example = "/var/backup/vaultwarden";
     };
 
     config = mkOption {
       type = attrsOf (nullOr (oneOf [ bool int str ]));
       default = {
-        config = {
-          ROCKET_ADDRESS = "::1"; # default to localhost
-          ROCKET_PORT = 8222;
-        };
+        ROCKET_ADDRESS = "::1"; # default to localhost
+        ROCKET_PORT = 8222;
       };
       example = literalExpression ''
         {
@@ -158,12 +157,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = package;
-      default = pkgs.vaultwarden;
-      defaultText = literalExpression "pkgs.vaultwarden";
-      description = lib.mdDoc "Vaultwarden package to use.";
-    };
+    package = mkPackageOption pkgs "vaultwarden" { };
 
     webVaultPackage = mkOption {
       type = package;
@@ -237,6 +231,13 @@ in {
       };
       wantedBy = [ "multi-user.target" ];
     };
+
+    systemd.tmpfiles.settings = mkIf (cfg.backupDir != null) {
+      "10-vaultwarden".${cfg.backupDir}.d = {
+        inherit user group;
+        mode = "0770";
+      };
+    };
   };
 
   # uses attributes of the linked package
diff --git a/nixos/modules/services/security/yubikey-agent.nix b/nixos/modules/services/security/yubikey-agent.nix
index ee57ec8bf8120..a9f15e4405f23 100644
--- a/nixos/modules/services/security/yubikey-agent.nix
+++ b/nixos/modules/services/security/yubikey-agent.nix
@@ -30,14 +30,7 @@ in
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.yubikey-agent;
-        defaultText = literalExpression "pkgs.yubikey-agent";
-        description = lib.mdDoc ''
-          The package used for the yubikey-agent daemon.
-        '';
-      };
+      package = mkPackageOption pkgs "yubikey-agent" { };
     };
   };
 
diff --git a/nixos/modules/services/system/automatic-timezoned.nix b/nixos/modules/services/system/automatic-timezoned.nix
index 9bdd64dd33a3c..8934ed3a7ef28 100644
--- a/nixos/modules/services/system/automatic-timezoned.nix
+++ b/nixos/modules/services/system/automatic-timezoned.nix
@@ -18,14 +18,7 @@ in
           the timezone.
         '';
       };
-      package = mkOption {
-        type = types.package;
-        default = pkgs.automatic-timezoned;
-        defaultText = literalExpression "pkgs.automatic-timezoned";
-        description = mdDoc ''
-          Which `automatic-timezoned` package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "automatic-timezoned" { };
     };
   };
 
diff --git a/nixos/modules/services/system/bpftune.nix b/nixos/modules/services/system/bpftune.nix
index d656a19c0ad1e..7106d5e4f78e3 100644
--- a/nixos/modules/services/system/bpftune.nix
+++ b/nixos/modules/services/system/bpftune.nix
@@ -11,7 +11,7 @@ in
     services.bpftune = {
       enable = lib.mkEnableOption (lib.mdDoc "bpftune BPF driven auto-tuning");
 
-      package = lib.mkPackageOptionMD pkgs "bpftune" { };
+      package = lib.mkPackageOption pkgs "bpftune" { };
     };
   };
 
diff --git a/nixos/modules/services/system/cachix-agent/default.nix b/nixos/modules/services/system/cachix-agent/default.nix
index 06494ddb631af..f8020fe970f1b 100644
--- a/nixos/modules/services/system/cachix-agent/default.nix
+++ b/nixos/modules/services/system/cachix-agent/default.nix
@@ -35,12 +35,7 @@ in {
       description = lib.mdDoc "Cachix uri to use.";
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.cachix;
-      defaultText = literalExpression "pkgs.cachix";
-      description = lib.mdDoc "Cachix Client package to use.";
-    };
+    package = mkPackageOption pkgs "cachix" { };
 
     credentialsFile = mkOption {
       type = types.path;
@@ -54,6 +49,7 @@ in {
   config = mkIf cfg.enable {
     systemd.services.cachix-agent = {
       description = "Cachix Deploy Agent";
+      wants = [ "network-online.target" ];
       after = ["network-online.target"];
       path = [ config.nix.package ];
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/system/cachix-watch-store.nix b/nixos/modules/services/system/cachix-watch-store.nix
index 89157b460b9a4..d48af29465aa5 100644
--- a/nixos/modules/services/system/cachix-watch-store.nix
+++ b/nixos/modules/services/system/cachix-watch-store.nix
@@ -23,6 +23,14 @@ in
       '';
     };
 
+    signingKeyFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc ''
+        Optional file containing a self-managed signing key to sign uploaded store paths.
+      '';
+      default = null;
+    };
+
     compressionLevel = mkOption {
       type = types.nullOr types.int;
       description = lib.mdDoc "The compression level for ZSTD compression (between 0 and 16)";
@@ -47,18 +55,13 @@ in
       default = false;
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.cachix;
-      defaultText = literalExpression "pkgs.cachix";
-      description = lib.mdDoc "Cachix Client package to use.";
-    };
-
+    package = mkPackageOption pkgs "cachix" { };
   };
 
   config = mkIf cfg.enable {
     systemd.services.cachix-watch-store-agent = {
       description = "Cachix watch store Agent";
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       path = [ config.nix.package ];
       wantedBy = [ "multi-user.target" ];
@@ -75,7 +78,8 @@ in
         DynamicUser = true;
         LoadCredential = [
           "cachix-token:${toString cfg.cachixTokenFile}"
-        ];
+        ]
+        ++ lib.optional (cfg.signingKeyFile != null) "signing-key:${toString cfg.signingKeyFile}";
       };
       script =
         let
@@ -86,6 +90,7 @@ in
         in
         ''
           export CACHIX_AUTH_TOKEN="$(<"$CREDENTIALS_DIRECTORY/cachix-token")"
+          ${lib.optionalString (cfg.signingKeyFile != null) ''export CACHIX_SIGNING_KEY="$(<"$CREDENTIALS_DIRECTORY/signing-key")"''}
           ${lib.escapeShellArgs command}
         '';
     };
diff --git a/nixos/modules/services/system/cloud-init.nix b/nixos/modules/services/system/cloud-init.nix
index d782bb1a36668..00ae77be4271c 100644
--- a/nixos/modules/services/system/cloud-init.nix
+++ b/nixos/modules/services/system/cloud-init.nix
@@ -164,7 +164,10 @@ in
     systemd.services.cloud-init-local = {
       description = "Initial cloud-init job (pre-networking)";
       wantedBy = [ "multi-user.target" ];
-      before = [ "systemd-networkd.service" ];
+      # In certain environments (AWS for example), cloud-init-local will
+      # first configure an IP through DHCP, and later delete it.
+      # This can cause race conditions with anything else trying to set IP through DHCP.
+      before = [ "systemd-networkd.service" "dhcpcd.service" ];
       path = path;
       serviceConfig = {
         Type = "oneshot";
diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix
index 8d5b25e617625..e8f8b48d0337f 100644
--- a/nixos/modules/services/system/dbus.nix
+++ b/nixos/modules/services/system/dbus.nix
@@ -95,6 +95,7 @@ in
         uid = config.ids.uids.messagebus;
         description = "D-Bus system message bus daemon user";
         home = homeDir;
+        homeMode = "0755";
         group = "messagebus";
       };
 
@@ -184,6 +185,11 @@ in
         aliases = [
           "dbus.service"
         ];
+        unitConfig = {
+          # We get errors when reloading the dbus-broker service
+          # if /tmp got remounted after this service started
+          RequiresMountsFor = [ "/tmp" ];
+        };
         # Don't restart dbus. Bad things tend to happen if we do.
         reloadIfChanged = true;
         restartTriggers = [
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index 3f501d4534603..38805eba2ca10 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -11,7 +11,7 @@ let
 in
 {
   options.services.earlyoom = {
-    enable = mkEnableOption (lib.mdDoc "Early out of memory killing");
+    enable = mkEnableOption (lib.mdDoc "early out of memory killing");
 
     freeMemThreshold = mkOption {
       type = types.ints.between 1 100;
diff --git a/nixos/modules/services/system/kerberos/default.nix b/nixos/modules/services/system/kerberos/default.nix
index 4ed48e463741a..486d4b49c195a 100644
--- a/nixos/modules/services/system/kerberos/default.nix
+++ b/nixos/modules/services/system/kerberos/default.nix
@@ -3,7 +3,7 @@
 let
   inherit (lib) mkOption mkIf types length attrNames;
   cfg = config.services.kerberos_server;
-  kerberos = config.krb5.kerberos;
+  kerberos = config.security.krb5.package;
 
   aclEntry = {
     options = {
diff --git a/nixos/modules/services/system/kerberos/heimdal.nix b/nixos/modules/services/system/kerberos/heimdal.nix
index 837c59caa5620..ecafc92766704 100644
--- a/nixos/modules/services/system/kerberos/heimdal.nix
+++ b/nixos/modules/services/system/kerberos/heimdal.nix
@@ -4,7 +4,7 @@ let
   inherit (lib) mkIf concatStringsSep concatMapStrings toList mapAttrs
     mapAttrsToList;
   cfg = config.services.kerberos_server;
-  kerberos = config.krb5.kerberos;
+  kerberos = config.security.krb5.package;
   stateDir = "/var/heimdal";
   aclFiles = mapAttrs
     (name: {acl, ...}: pkgs.writeText "${name}.acl" (concatMapStrings ((
@@ -35,7 +35,7 @@ in
         mkdir -m 0755 -p ${stateDir}
       '';
       serviceConfig.ExecStart =
-        "${kerberos}/libexec/heimdal/kadmind --config-file=/etc/heimdal-kdc/kdc.conf";
+        "${kerberos}/libexec/kadmind --config-file=/etc/heimdal-kdc/kdc.conf";
       restartTriggers = [ kdcConfFile ];
     };
 
@@ -46,7 +46,7 @@ in
         mkdir -m 0755 -p ${stateDir}
       '';
       serviceConfig.ExecStart =
-        "${kerberos}/libexec/heimdal/kdc --config-file=/etc/heimdal-kdc/kdc.conf";
+        "${kerberos}/libexec/kdc --config-file=/etc/heimdal-kdc/kdc.conf";
       restartTriggers = [ kdcConfFile ];
     };
 
@@ -56,7 +56,7 @@ in
       preStart = ''
         mkdir -m 0755 -p ${stateDir}
       '';
-      serviceConfig.ExecStart = "${kerberos}/libexec/heimdal/kpasswdd";
+      serviceConfig.ExecStart = "${kerberos}/libexec/kpasswdd";
       restartTriggers = [ kdcConfFile ];
     };
 
diff --git a/nixos/modules/services/system/kerberos/mit.nix b/nixos/modules/services/system/kerberos/mit.nix
index 112000140453f..a654bd1fe7e1b 100644
--- a/nixos/modules/services/system/kerberos/mit.nix
+++ b/nixos/modules/services/system/kerberos/mit.nix
@@ -4,7 +4,7 @@ let
   inherit (lib) mkIf concatStrings concatStringsSep concatMapStrings toList
     mapAttrs mapAttrsToList;
   cfg = config.services.kerberos_server;
-  kerberos = config.krb5.kerberos;
+  kerberos = config.security.krb5.package;
   stateDir = "/var/lib/krb5kdc";
   PIDFile = "/run/kdc.pid";
   aclMap = {
diff --git a/nixos/modules/services/system/nix-daemon.nix b/nixos/modules/services/system/nix-daemon.nix
index c9df20196dbd9..ce255cd8d0a46 100644
--- a/nixos/modules/services/system/nix-daemon.nix
+++ b/nixos/modules/services/system/nix-daemon.nix
@@ -249,11 +249,6 @@ in
 
     services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers;
 
-    system.activationScripts.nix = stringAfter [ "etc" "users" ]
-      ''
-        install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user
-      '';
-
     # Legacy configuration conversion.
     nix.settings = mkMerge [
       (mkIf (isNixAtLeast "2.3pre") { sandbox-fallback = false; })
diff --git a/nixos/modules/services/system/saslauthd.nix b/nixos/modules/services/system/saslauthd.nix
index 09720146aaa90..9424b6c51fc1f 100644
--- a/nixos/modules/services/system/saslauthd.nix
+++ b/nixos/modules/services/system/saslauthd.nix
@@ -18,12 +18,7 @@ in
 
       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 = lib.mdDoc "Cyrus SASL package to use.";
-      };
+      package = mkPackageOption pkgs [ "cyrus_sasl" "bin" ] { };
 
       mechanism = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/system/systembus-notify.nix b/nixos/modules/services/system/systembus-notify.nix
index 269197b3997e3..f79879fa13606 100644
--- a/nixos/modules/services/system/systembus-notify.nix
+++ b/nixos/modules/services/system/systembus-notify.nix
@@ -13,7 +13,7 @@ in
 
       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.
+      local user to DoS your session by spamming notifications
     '');
   };
 
diff --git a/nixos/modules/services/system/zram-generator.nix b/nixos/modules/services/system/zram-generator.nix
index 5902eda556967..429531e5743d8 100644
--- a/nixos/modules/services/system/zram-generator.nix
+++ b/nixos/modules/services/system/zram-generator.nix
@@ -11,7 +11,7 @@ in
   options.services.zram-generator = {
     enable = lib.mkEnableOption (lib.mdDoc "Systemd unit generator for zram devices");
 
-    package = lib.mkPackageOptionMD pkgs "zram-generator" { };
+    package = lib.mkPackageOption pkgs "zram-generator" { };
 
     settings = lib.mkOption {
       type = lib.types.submodule {
@@ -27,7 +27,7 @@ in
 
   config = lib.mkIf cfg.enable {
     system.requiredKernelConfig = with config.lib.kernelConfig; [
-      (isModule "ZRAM")
+      (isEnabled "ZRAM")
     ];
 
     systemd.packages = [ cfg.package ];
diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix
index 003f7b2613b76..4802e3e1c63ac 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -147,13 +147,7 @@ in {
           '';
         };
 
-        package = mkOption {
-          type = types.package;
-          example = literalExpression "pkgs.deluge-2_x";
-          description = lib.mdDoc ''
-            Deluge package to use.
-          '';
-        };
+        package = mkPackageOption pkgs "deluge-2_x" { };
       };
 
       deluge.web = {
diff --git a/nixos/modules/services/torrent/flexget.nix b/nixos/modules/services/torrent/flexget.nix
index 1b971838b32e0..bc06b34a1f9ec 100644
--- a/nixos/modules/services/torrent/flexget.nix
+++ b/nixos/modules/services/torrent/flexget.nix
@@ -14,9 +14,9 @@ let
 in {
   options = {
     services.flexget = {
-      enable = mkEnableOption (lib.mdDoc "Run FlexGet Daemon");
+      enable = mkEnableOption (lib.mdDoc "FlexGet daemon");
 
-      package = mkPackageOptionMD pkgs "flexget" {};
+      package = mkPackageOption pkgs "flexget" {};
 
       user = mkOption {
         default = "deluge";
@@ -64,7 +64,6 @@ in {
         path = [ pkg ];
         serviceConfig = {
           User = cfg.user;
-          Environment = "TZ=${config.time.timeZone}";
           ExecStartPre = "${pkgs.coreutils}/bin/install -m644 ${ymlFile} ${configFile}";
           ExecStart = "${pkg}/bin/flexget -c ${configFile} daemon start";
           ExecStop = "${pkg}/bin/flexget -c ${configFile} daemon stop";
diff --git a/nixos/modules/services/torrent/opentracker.nix b/nixos/modules/services/torrent/opentracker.nix
index 7d67491c11917..71852f24e55b2 100644
--- a/nixos/modules/services/torrent/opentracker.nix
+++ b/nixos/modules/services/torrent/opentracker.nix
@@ -7,14 +7,7 @@ in {
   options.services.opentracker = {
     enable = mkEnableOption (lib.mdDoc "opentracker");
 
-    package = mkOption {
-      type = types.package;
-      description = lib.mdDoc ''
-        opentracker package to use
-      '';
-      default = pkgs.opentracker;
-      defaultText = literalExpression "pkgs.opentracker";
-    };
+    package = mkPackageOption pkgs "opentracker" { };
 
     extraOptions = mkOption {
       type = types.separatedString " ";
diff --git a/nixos/modules/services/torrent/rtorrent.nix b/nixos/modules/services/torrent/rtorrent.nix
index 64cda7fb675f8..699f3be82a9d8 100644
--- a/nixos/modules/services/torrent/rtorrent.nix
+++ b/nixos/modules/services/torrent/rtorrent.nix
@@ -53,14 +53,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.rtorrent;
-      defaultText = literalExpression "pkgs.rtorrent";
-      description = lib.mdDoc ''
-        The rtorrent package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "rtorrent" { };
 
     port = mkOption {
       type = types.port;
diff --git a/nixos/modules/services/torrent/torrentstream.nix b/nixos/modules/services/torrent/torrentstream.nix
new file mode 100644
index 0000000000000..27aad06130e3c
--- /dev/null
+++ b/nixos/modules/services/torrent/torrentstream.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.torrentstream;
+  dataDir = "/var/lib/torrentstream/";
+in
+{
+  options.services.torrentstream = {
+    enable = lib.mkEnableOption (lib.mdDoc "TorrentStream daemon");
+    package = lib.mkPackageOption pkgs "torrentstream" { };
+    port = lib.mkOption {
+      type = lib.types.port;
+      default = 5082;
+      description = lib.mdDoc ''
+        TorrentStream port.
+      '';
+    };
+    openFirewall = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Open ports in the firewall for TorrentStream daemon.
+      '';
+    };
+    address = lib.mkOption {
+      type = lib.types.str;
+      default = "0.0.0.0";
+      description = lib.mdDoc ''
+        Address to listen on.
+      '';
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    systemd.services.torrentstream = {
+      after = [ "network.target" ];
+      description = "TorrentStream Daemon";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = lib.getExe cfg.package;
+        Restart = "on-failure";
+        UMask = "077";
+        StateDirectory = "torrentstream";
+        DynamicUser = true;
+      };
+      environment = {
+        WEB_PORT = toString cfg.port;
+        DOWNLOAD_PATH = "%S/torrentstream";
+        LISTEN_ADDR = cfg.address;
+      };
+    };
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
+  };
+}
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index 752ab91fe6315..5dd02eb331633 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -148,7 +148,7 @@ in
             type = types.bool;
             default = true;
             description = lib.mdDoc ''
-              Whether to enable [Micro Transport Protocol (µTP)](http://en.wikipedia.org/wiki/Micro_Transport_Protocol).
+              Whether to enable [Micro Transport Protocol (µTP)](https://en.wikipedia.org/wiki/Micro_Transport_Protocol).
             '';
           };
           options.watch-dir = mkOption {
@@ -174,7 +174,7 @@ in
         };
       };
 
-      package = mkPackageOptionMD pkgs "transmission" {};
+      package = mkPackageOption pkgs "transmission" {};
 
       downloadDirPermissions = mkOption {
         type = with types; nullOr str;
@@ -182,7 +182,7 @@ in
         example = "770";
         description = lib.mdDoc ''
           If not `null`, is used as the permissions
-          set by `systemd.activationScripts.transmission-daemon`
+          set by `system.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).
@@ -251,6 +251,20 @@ in
           For instance, SSH sessions may time out more easily.
         '';
       };
+
+      webHome = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "pkgs.flood-for-transmission";
+        description = lib.mdDoc ''
+          If not `null`, sets the value of the `TRANSMISSION_WEB_HOME`
+          environment variable used by the service. Useful for overriding
+          the web interface files, without overriding the transmission
+          package and thus requiring rebuilding it locally. Use this if
+          you want to use an alternative web interface, such as
+          `pkgs.flood-for-transmission`.
+        '';
+      };
     };
   };
 
@@ -280,6 +294,7 @@ in
       requires = optional apparmor.enable "apparmor.service";
       wantedBy = [ "multi-user.target" ];
       environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
+      environment.TRANSMISSION_WEB_HOME = lib.mkIf (cfg.webHome != null) cfg.webHome;
 
       serviceConfig = {
         # Use "+" because credentialsFile may not be accessible to User= or Group=.
@@ -314,6 +329,9 @@ in
         BindPaths =
           [ "${cfg.home}/${settingsDir}"
             cfg.settings.download-dir
+            # Transmission may need to read in the host's /run (eg. /run/systemd/resolve)
+            # or write in its private /run (eg. /run/host).
+            "/run"
           ] ++
           optional cfg.settings.incomplete-dir-enabled
             cfg.settings.incomplete-dir ++
@@ -324,7 +342,6 @@ in
           # an AppArmor profile is provided to get a confinement based upon paths and rights.
           builtins.storeDir
           "/etc"
-          "/run"
           ] ++
           optional (cfg.settings.script-torrent-done-enabled &&
                     cfg.settings.script-torrent-done-filename != null)
@@ -333,10 +350,10 @@ in
             cfg.settings.watch-dir;
         StateDirectory = [
           "transmission"
-          "transmission/.config/transmission-daemon"
-          "transmission/.incomplete"
-          "transmission/Downloads"
-          "transmission/watch-dir"
+          "transmission/${settingsDir}"
+          "transmission/${incompleteDir}"
+          "transmission/${downloadsDir}"
+          "transmission/${watchDir}"
         ];
         StateDirectoryMode = mkDefault 750;
         # The following options are only for optimizing:
@@ -349,10 +366,10 @@ in
         MemoryDenyWriteExecute = true;
         NoNewPrivileges = true;
         PrivateDevices = true;
-        PrivateMounts = true;
+        PrivateMounts = mkDefault true;
         PrivateNetwork = mkDefault false;
         PrivateTmp = true;
-        PrivateUsers = true;
+        PrivateUsers = mkDefault true;
         ProtectClock = true;
         ProtectControlGroups = true;
         # ProtectHome=true would not allow BindPaths= to work across /home,
@@ -434,7 +451,7 @@ in
       # at least up to the values hardcoded here:
       (mkIf cfg.settings.utp-enabled {
         "net.core.rmem_max" = mkDefault 4194304; # 4MB
-        "net.core.wmem_max" = mkDefault "1048576"; # 1MB
+        "net.core.wmem_max" = mkDefault 1048576; # 1MB
       })
       (mkIf cfg.performanceNetParameters {
         # Increase the number of available source (local) TCP and UDP ports to 49151.
@@ -490,6 +507,10 @@ in
         # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
         px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
       ''}
+
+      ${optionalString (cfg.webHome != null) ''
+        r ${cfg.webHome}/**,
+      ''}
     '';
   };
 
diff --git a/nixos/modules/services/video/epgstation/default.nix b/nixos/modules/services/video/epgstation/default.nix
index fca483b0dbd76..a7468e7cc2b63 100644
--- a/nixos/modules/services/video/epgstation/default.nix
+++ b/nixos/modules/services/video/epgstation/default.nix
@@ -80,11 +80,11 @@ in
   options.services.epgstation = {
     enable = lib.mkEnableOption (lib.mdDoc description);
 
-    package = lib.mkPackageOptionMD pkgs "epgstation" { };
+    package = lib.mkPackageOption pkgs "epgstation" { };
 
-    ffmpeg = lib.mkPackageOptionMD pkgs "ffmpeg" {
-      default = [ "ffmpeg-headless" ];
-      example = "pkgs.ffmpeg-full";
+    ffmpeg = lib.mkPackageOption pkgs "ffmpeg" {
+      default = "ffmpeg-headless";
+      example = "ffmpeg-full";
     };
 
     usePreconfiguredStreaming = lib.mkOption {
diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix
index 8db2bfae80ac0..b7945282ba09b 100644
--- a/nixos/modules/services/video/frigate.nix
+++ b/nixos/modules/services/video/frigate.nix
@@ -10,6 +10,7 @@ let
     mkDefault
     mdDoc
     mkEnableOption
+    mkPackageOption
     mkIf
     mkOption
     types;
@@ -62,13 +63,7 @@ in
   options.services.frigate = with types; {
     enable = mkEnableOption (mdDoc "Frigate NVR");
 
-    package = mkOption {
-      type = package;
-      default = pkgs.frigate;
-      description = mdDoc ''
-        The frigate package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "frigate" { };
 
     hostname = mkOption {
       type = str;
@@ -358,6 +353,7 @@ in
       ];
       serviceConfig = {
         ExecStart = "${cfg.package.python.interpreter} -m frigate";
+        Restart = "on-failure";
 
         User = "frigate";
         Group = "frigate";
diff --git a/nixos/modules/services/video/go2rtc/default.nix b/nixos/modules/services/video/go2rtc/default.nix
index 1151d31b68e66..9dddbb60baa80 100644
--- a/nixos/modules/services/video/go2rtc/default.nix
+++ b/nixos/modules/services/video/go2rtc/default.nix
@@ -11,7 +11,7 @@ let
     mdDoc
     mkEnableOption
     mkOption
-    mkPackageOptionMD
+    mkPackageOption
     types
     ;
 
@@ -28,7 +28,7 @@ in
   options.services.go2rtc = with types; {
     enable = mkEnableOption (mdDoc "go2rtc streaming server");
 
-    package = mkPackageOptionMD pkgs "go2rtc" { };
+    package = mkPackageOption pkgs "go2rtc" { };
 
     settings = mkOption {
       default = {};
@@ -94,6 +94,7 @@ in
 
   config = lib.mkIf cfg.enable {
     systemd.services.go2rtc = {
+      wants = [ "network-online.target" ];
       after = [
         "network-online.target"
       ];
diff --git a/nixos/modules/services/video/mediamtx.nix b/nixos/modules/services/video/mediamtx.nix
index c3abd9cdcc5cb..f741dea59e3ed 100644
--- a/nixos/modules/services/video/mediamtx.nix
+++ b/nixos/modules/services/video/mediamtx.nix
@@ -11,7 +11,7 @@ in
     services.mediamtx = {
       enable = lib.mkEnableOption (lib.mdDoc "MediaMTX");
 
-      package = lib.mkPackageOptionMD pkgs "mediamtx" { };
+      package = lib.mkPackageOption pkgs "mediamtx" { };
 
       settings = lib.mkOption {
         description = lib.mdDoc ''
@@ -40,7 +40,7 @@ in
       };
 
       allowVideoAccess = lib.mkEnableOption (lib.mdDoc ''
-        Enable access to video devices like cameras on the system.
+        access to video devices like cameras on the system
       '');
     };
   };
diff --git a/nixos/modules/services/video/unifi-video.nix b/nixos/modules/services/video/unifi-video.nix
index cb438a08150f7..518977e49baef 100644
--- a/nixos/modules/services/video/unifi-video.nix
+++ b/nixos/modules/services/video/unifi-video.nix
@@ -32,7 +32,7 @@ let
     name = "mongo.conf";
     executable = false;
     text = ''
-      # for documentation of all options, see http://docs.mongodb.org/manual/reference/configuration-options/
+      # for documentation of all options, see https://www.mongodb.com/docs/manual/reference/configuration-options/
 
       storage:
          dbPath: ${cfg.dataDir}/db
@@ -63,7 +63,7 @@ let
     executable = false;
     text = ''
       # for documentation of all options, see:
-      #   http://docs.mongodb.org/manual/reference/configuration-options/
+      #   https://www.mongodb.com/docs/manual/reference/configuration-options/
 
       storage:
          dbPath: ${cfg.dataDir}/db-wt
@@ -103,31 +103,12 @@ in
       '';
     };
 
-    jrePackage = mkOption {
-      type = types.package;
-      default = pkgs.jre8;
-      defaultText = literalExpression "pkgs.jre8";
-      description = lib.mdDoc ''
-        The JRE package to use. Check the release notes to ensure it is supported.
-      '';
-    };
+    jrePackage = mkPackageOption pkgs "jre8" { };
 
-    unifiVideoPackage = mkOption {
-      type = types.package;
-      default = pkgs.unifi-video;
-      defaultText = literalExpression "pkgs.unifi-video";
-      description = lib.mdDoc ''
-        The unifi-video package to use.
-      '';
-    };
+    unifiVideoPackage = mkPackageOption pkgs "unifi-video" { };
 
-    mongodbPackage = mkOption {
-      type = types.package;
-      default = pkgs.mongodb-4_4;
-      defaultText = literalExpression "pkgs.mongodb";
-      description = lib.mdDoc ''
-        The mongodb package to use.
-      '';
+    mongodbPackage = mkPackageOption pkgs "mongodb" {
+      default = "mongodb-4_4";
     };
 
     logDir = mkOption {
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix
index 8d17752586128..4cd9e26643787 100644
--- a/nixos/modules/services/web-apps/akkoma.nix
+++ b/nixos/modules/services/web-apps/akkoma.nix
@@ -86,7 +86,7 @@ let
   # Erlang/Elixir uses a somewhat special format for IP addresses
   erlAddr = addr: fileContents
     (pkgs.runCommand addr {
-      nativeBuildInputs = with pkgs; [ elixir ];
+      nativeBuildInputs = [ cfg.package.elixirPackage ];
       code = ''
         case :inet.parse_address('${addr}') do
           {:ok, addr} -> IO.inspect addr
@@ -96,7 +96,7 @@ let
       passAsFile = [ "code" ];
     } ''elixir "$codePath" >"$out"'');
 
-  format = pkgs.formats.elixirConf { };
+  format = pkgs.formats.elixirConf { elixir = cfg.package.elixirPackage; };
   configFile = format.generate "config.exs"
     (replaceSec
       (attrsets.updateManyAttrsByPath [{
@@ -146,7 +146,7 @@ let
 
   initSecretsScript = writeShell {
     name = "akkoma-init-secrets";
-    runtimeInputs = with pkgs; [ coreutils elixir ];
+    runtimeInputs = with pkgs; [ coreutils cfg.package.elixirPackage ];
     text = let
       key-base = web.secret_key_base;
       jwt-signer = ex.":joken".":default_signer";
@@ -282,11 +282,11 @@ let
         AKKOMA_CONFIG_PATH="$RUNTIME_DIRECTORY/config.exs" \
         ERL_EPMD_ADDRESS="${cfg.dist.address}" \
         ERL_EPMD_PORT="${toString cfg.dist.epmdPort}" \
-        ERL_FLAGS="${concatStringsSep " " [
-          "-kernel inet_dist_use_interface '${erlAddr cfg.dist.address}'"
-          "-kernel inet_dist_listen_min ${toString cfg.dist.portMin}"
-          "-kernel inet_dist_listen_max ${toString cfg.dist.portMax}"
-        ]}" \
+        ERL_FLAGS=${lib.escapeShellArg (lib.escapeShellArgs ([
+          "-kernel" "inet_dist_use_interface" (erlAddr cfg.dist.address)
+          "-kernel" "inet_dist_listen_min" (toString cfg.dist.portMin)
+          "-kernel" "inet_dist_listen_max" (toString cfg.dist.portMax)
+        ] ++ cfg.dist.extraFlags))} \
         RELEASE_COOKIE="$(<"$RUNTIME_DIRECTORY/cookie")" \
         RELEASE_NAME="akkoma" \
           exec "${cfg.package}/bin/$(basename "$0")" "$@"
@@ -352,12 +352,7 @@ in {
     services.akkoma = {
       enable = mkEnableOption (mdDoc "Akkoma");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.akkoma;
-        defaultText = literalExpression "pkgs.akkoma";
-        description = mdDoc "Akkoma package to use.";
-      };
+      package = mkPackageOption pkgs "akkoma" { };
 
       user = mkOption {
         type = types.nonEmptyStr;
@@ -553,6 +548,13 @@ in {
           description = mdDoc "TCP port to bind Erlang Port Mapper Daemon to.";
         };
 
+        extraFlags = mkOption {
+          type = with types; listOf str;
+          default = [ ];
+          description = mdDoc "Extra flags to pass to Erlang";
+          example = [ "+sbwt" "none" "+sbwtdcpu" "none" "+sbwtdio" "none" ];
+        };
+
         portMin = mkOption {
           type = types.port;
           default = 49152;
@@ -902,7 +904,7 @@ in {
   };
 
   config = mkIf cfg.enable {
-    warnings = optionals (!config.security.sudo.enable) [''
+    warnings = optionals (with config.security; (!sudo.enable) && (!sudo-rs.enable)) [''
       The pleroma_ctl wrapper enabled by the installWrapper option relies on
       sudo, which appears to have been disabled through security.sudo.enable.
     ''];
@@ -972,7 +974,7 @@ in {
       # This service depends on network-online.target and is sequenced after
       # it because it requires access to the Internet to function properly.
       bindsTo = [ "akkoma-config.service" ];
-      wants = [ "network-online.service" ];
+      wants = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       after = [
         "akkoma-config.target"
diff --git a/nixos/modules/services/web-apps/alps.nix b/nixos/modules/services/web-apps/alps.nix
index 05fb676102df4..81c6b8ad30b5f 100644
--- a/nixos/modules/services/web-apps/alps.nix
+++ b/nixos/modules/services/web-apps/alps.nix
@@ -94,6 +94,7 @@ in {
       description = "alps is a simple and extensible webmail.";
       documentation = [ "https://git.sr.ht/~migadu/alps" ];
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network.target" "network-online.target" ];
 
       serviceConfig = {
diff --git a/nixos/modules/services/web-apps/anuko-time-tracker.nix b/nixos/modules/services/web-apps/anuko-time-tracker.nix
index f43cbc40ec7aa..3b326390fa43f 100644
--- a/nixos/modules/services/web-apps/anuko-time-tracker.nix
+++ b/nixos/modules/services/web-apps/anuko-time-tracker.nix
@@ -58,7 +58,7 @@ in
   options.services.anuko-time-tracker = {
     enable = lib.mkEnableOption (lib.mdDoc "Anuko Time Tracker");
 
-    package = lib.mkPackageOptionMD pkgs "anuko-time-tracker" {};
+    package = lib.mkPackageOption pkgs "anuko-time-tracker" {};
 
     database = {
       createLocally = lib.mkOption {
diff --git a/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
index fe98c1777ea05..aa13659fcc305 100644
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -133,18 +133,14 @@ in
         };
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.atlassian-confluence;
-        defaultText = literalExpression "pkgs.atlassian-confluence";
-        description = lib.mdDoc "Atlassian Confluence package to use.";
-      };
-
-      jrePackage = mkOption {
-        type = types.package;
-        default = pkgs.oraclejre8;
-        defaultText = literalExpression "pkgs.oraclejre8";
-        description = lib.mdDoc "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+      package = mkPackageOption pkgs "atlassian-confluence" { };
+
+      jrePackage = mkPackageOption pkgs "oraclejre8" {
+        extraDescription = ''
+        ::: {.note }
+        Atlassian only supports the Oracle JRE (JRASERVER-46152).
+        :::
+        '';
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
index c8d1eaef31d88..eed1a127fe4f1 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -115,18 +115,14 @@ in
         };
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.atlassian-crowd;
-        defaultText = literalExpression "pkgs.atlassian-crowd";
-        description = lib.mdDoc "Atlassian Crowd package to use.";
-      };
-
-      jrePackage = mkOption {
-        type = types.package;
-        default = pkgs.oraclejre8;
-        defaultText = literalExpression "pkgs.oraclejre8";
-        description = lib.mdDoc "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+      package = mkPackageOption pkgs "atlassian-crowd" { };
+
+      jrePackage = mkPackageOption pkgs "oraclejre8" {
+        extraDescription = ''
+        ::: {.note }
+        Atlassian only supports the Oracle JRE (JRASERVER-46152).
+        :::
+        '';
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix
index 4cc858216944c..a9f337810a0f9 100644
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -132,18 +132,14 @@ in
         };
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.atlassian-jira;
-        defaultText = literalExpression "pkgs.atlassian-jira";
-        description = lib.mdDoc "Atlassian JIRA package to use.";
-      };
-
-      jrePackage = mkOption {
-        type = types.package;
-        default = pkgs.oraclejre8;
-        defaultText = literalExpression "pkgs.oraclejre8";
-        description = lib.mdDoc "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+      package = mkPackageOption pkgs "atlassian-jira" { };
+
+      jrePackage = mkPackageOption pkgs "oraclejre8" {
+        extraDescription = ''
+        ::: {.note }
+        Atlassian only supports the Oracle JRE (JRASERVER-46152).
+        :::
+        '';
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/audiobookshelf.nix b/nixos/modules/services/web-apps/audiobookshelf.nix
new file mode 100644
index 0000000000000..84dffc5f9d3c5
--- /dev/null
+++ b/nixos/modules/services/web-apps/audiobookshelf.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.audiobookshelf;
+in
+{
+  options = {
+    services.audiobookshelf = {
+      enable = mkEnableOption "Audiobookshelf, self-hosted audiobook and podcast server.";
+
+      package = mkPackageOption pkgs "audiobookshelf" { };
+
+      dataDir = mkOption {
+        description = "Path to Audiobookshelf config and metadata inside of /var/lib.";
+        default = "audiobookshelf";
+        type = types.str;
+      };
+
+      host = mkOption {
+        description = "The host Audiobookshelf binds to.";
+        default = "127.0.0.1";
+        example = "0.0.0.0";
+        type = types.str;
+      };
+
+      port = mkOption {
+        description = "The TCP port Audiobookshelf will listen on.";
+        default = 8000;
+        type = types.port;
+      };
+
+      user = mkOption {
+        description = "User account under which Audiobookshelf runs.";
+        default = "audiobookshelf";
+        type = types.str;
+      };
+
+      group = mkOption {
+        description = "Group under which Audiobookshelf runs.";
+        default = "audiobookshelf";
+        type = types.str;
+      };
+
+      openFirewall = mkOption {
+        description = "Open ports in the firewall for the Audiobookshelf web interface.";
+        default = false;
+        type = types.bool;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.audiobookshelf = {
+      description = "Audiobookshelf is a self-hosted audiobook and podcast server";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = cfg.dataDir;
+        WorkingDirectory = "/var/lib/${cfg.dataDir}";
+        ExecStart = "${cfg.package}/bin/audiobookshelf --host ${cfg.host} --port ${toString cfg.port}";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users = mkIf (cfg.user == "audiobookshelf") {
+      audiobookshelf = {
+        isSystemUser = true;
+        group = cfg.group;
+        home = "/var/lib/${cfg.dataDir}";
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "audiobookshelf") {
+      audiobookshelf = { };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+  };
+
+  meta.maintainers = with maintainers; [ wietsedv ];
+}
diff --git a/nixos/modules/services/web-apps/c2fmzq-server.md b/nixos/modules/services/web-apps/c2fmzq-server.md
new file mode 100644
index 0000000000000..236953bd4ff7a
--- /dev/null
+++ b/nixos/modules/services/web-apps/c2fmzq-server.md
@@ -0,0 +1,42 @@
+# c2FmZQ {#module-services-c2fmzq}
+
+c2FmZQ is an application that can securely encrypt, store, and share files,
+including but not limited to pictures and videos.
+
+The service `c2fmzq-server` can be enabled by setting
+```
+{
+  services.c2fmzq-server.enable = true;
+}
+```
+This will spin up an instance of the server which is API-compatible with
+[Stingle Photos](https://stingle.org) and an experimental Progressive Web App
+(PWA) to interact with the storage via the browser.
+
+In principle the server can be exposed directly on a public interface and there
+are command line options to manage HTTPS certificates directly, but the module
+is designed to be served behind a reverse proxy or only accessed via localhost.
+
+```
+{
+  services.c2fmzq-server = {
+    enable = true;
+    bindIP = "127.0.0.1"; # default
+    port = 8080; # default
+  };
+
+  services.nginx = {
+    enable = true;
+    recommendedProxySettings = true;
+    virtualHosts."example.com" = {
+      enableACME = true;
+      forceSSL = true;
+      locations."/" = {
+        proxyPass = "http://127.0.0.1:8080";
+      };
+    };
+  };
+}
+```
+
+For more information, see <https://github.com/c2FmZQ/c2FmZQ/>.
diff --git a/nixos/modules/services/web-apps/c2fmzq-server.nix b/nixos/modules/services/web-apps/c2fmzq-server.nix
new file mode 100644
index 0000000000000..dee131182de16
--- /dev/null
+++ b/nixos/modules/services/web-apps/c2fmzq-server.nix
@@ -0,0 +1,130 @@
+{ lib, pkgs, config, ... }:
+
+let
+  inherit (lib) mkEnableOption mkPackageOption mkOption types;
+
+  cfg = config.services.c2fmzq-server;
+
+  argsFormat = {
+    type = with lib.types; attrsOf (nullOr (oneOf [ bool int str ]));
+    generate = lib.cli.toGNUCommandLineShell {
+      mkBool = k: v: [
+        "--${k}=${if v then "true" else "false"}"
+      ];
+    };
+  };
+in {
+  options.services.c2fmzq-server = {
+    enable = mkEnableOption "c2fmzq-server";
+
+    bindIP = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = "The local address to use.";
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = "The local port to use.";
+    };
+
+    passphraseFile = mkOption {
+      type = types.str;
+      example = "/run/secrets/c2fmzq/pwfile";
+      description = "Path to file containing the database passphrase";
+    };
+
+    package = mkPackageOption pkgs "c2fmzq" { };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = argsFormat.type;
+
+        options = {
+          address = mkOption {
+            internal = true;
+            type = types.str;
+            default = "${cfg.bindIP}:${toString cfg.port}";
+          };
+
+          database = mkOption {
+            type = types.str;
+            default = "%S/c2fmzq-server/data";
+            description = "Path of the database";
+          };
+
+          verbose = mkOption {
+            type = types.ints.between 1 3;
+            default = 2;
+            description = "The level of logging verbosity: 1:Error 2:Info 3:Debug";
+          };
+        };
+      };
+      description = ''
+        Configuration for c2FmZQ-server passed as CLI arguments.
+        Run {command}`c2FmZQ-server help` for supported values.
+      '';
+      example = {
+        verbose = 3;
+        allow-new-accounts = true;
+        auto-approve-new-accounts = true;
+        encrypt-metadata = true;
+        enable-webapp = true;
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.c2fmzq-server = {
+      description = "c2FmZQ-server";
+      documentation = [ "https://github.com/c2FmZQ/c2FmZQ/blob/main/README.md" ];
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network.target" "network-online.target" ];
+
+      serviceConfig = {
+        ExecStart = "${lib.getExe cfg.package} ${argsFormat.generate cfg.settings}";
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        DynamicUser = true;
+        Environment = "C2FMZQ_PASSPHRASE_FILE=%d/passphrase-file";
+        IPAccounting = true;
+        IPAddressAllow = cfg.bindIP;
+        IPAddressDeny = "any";
+        LoadCredential = "passphrase-file:${cfg.passphraseFile}";
+        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";
+        StateDirectory = "c2fmzq-server";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged @obsolete" ];
+      };
+    };
+  };
+
+  meta = {
+    doc = ./c2fmzq-server.md;
+    maintainers = with lib.maintainers; [ hmenke ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/calibre-web.nix b/nixos/modules/services/web-apps/calibre-web.nix
index 143decfc09172..80567db10c972 100644
--- a/nixos/modules/services/web-apps/calibre-web.nix
+++ b/nixos/modules/services/web-apps/calibre-web.nix
@@ -10,6 +10,8 @@ in
     services.calibre-web = {
       enable = mkEnableOption (lib.mdDoc "Calibre-Web");
 
+      package = lib.mkPackageOption pkgs "calibre-web" { };
+
       listen = {
         ip = mkOption {
           type = types.str;
@@ -73,6 +75,8 @@ in
           '';
         };
 
+        enableKepubify = mkEnableOption (lib.mdDoc "kebup conversion support");
+
         enableBookUploading = mkOption {
           type = types.bool;
           default = false;
@@ -106,7 +110,7 @@ in
     systemd.services.calibre-web = let
       appDb = "/var/lib/${cfg.dataDir}/app.db";
       gdriveDb = "/var/lib/${cfg.dataDir}/gdrive.db";
-      calibreWebCmd = "${pkgs.calibre-web}/bin/calibre-web -p ${appDb} -g ${gdriveDb}";
+      calibreWebCmd = "${cfg.package}/bin/calibre-web -p ${appDb} -g ${gdriveDb}";
 
       settings = concatStringsSep ", " (
         [
@@ -117,6 +121,7 @@ in
         ]
         ++ optional (cfg.options.calibreLibrary != null) "config_calibre_dir = '${cfg.options.calibreLibrary}'"
         ++ optional cfg.options.enableBookConversion "config_converterpath = '${pkgs.calibre}/bin/ebook-convert'"
+        ++ optional cfg.options.enableKepubify "config_kepubifypath = '${pkgs.kepubify}/bin/kepubify'"
       );
     in
       {
diff --git a/nixos/modules/services/web-apps/cloudlog.nix b/nixos/modules/services/web-apps/cloudlog.nix
index da2cf93d7f1c8..5519d6967a128 100644
--- a/nixos/modules/services/web-apps/cloudlog.nix
+++ b/nixos/modules/services/web-apps/cloudlog.nix
@@ -69,7 +69,7 @@ let
 in
 {
   options.services.cloudlog = with types; {
-    enable = mkEnableOption (mdDoc "Whether to enable Cloudlog");
+    enable = mkEnableOption (mdDoc "Cloudlog");
     dataDir = mkOption {
       type = str;
       default = "/var/lib/cloudlog";
diff --git a/nixos/modules/services/web-apps/code-server.nix b/nixos/modules/services/web-apps/code-server.nix
index 11601f6c30449..d087deb7848d0 100644
--- a/nixos/modules/services/web-apps/code-server.nix
+++ b/nixos/modules/services/web-apps/code-server.nix
@@ -205,6 +205,7 @@ in {
     systemd.services.code-server = {
       description = "Code server";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       path = cfg.extraPackages;
       environment = {
diff --git a/nixos/modules/services/web-apps/coder.nix b/nixos/modules/services/web-apps/coder.nix
index 469a29bc3aa8c..0f5cb2c3c6894 100644
--- a/nixos/modules/services/web-apps/coder.nix
+++ b/nixos/modules/services/web-apps/coder.nix
@@ -36,14 +36,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.coder;
-        description = lib.mdDoc ''
-          Package to use for the service.
-        '';
-        defaultText = literalExpression "pkgs.coder";
-      };
+      package = mkPackageOption pkgs "coder" { };
 
       homeDir = mkOption {
         type = types.str;
@@ -149,8 +142,8 @@ in {
 
   config = mkIf cfg.enable {
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.username == name;
-        message = "services.coder.database.username must be set to ${user} if services.coder.database.createLocally is set true";
+      { assertion = cfg.database.createLocally -> cfg.database.username == name && cfg.database.database == cfg.database.username;
+        message = "services.coder.database.username must be set to ${name} if services.coder.database.createLocally is set true";
       }
     ];
 
@@ -193,10 +186,8 @@ in {
         cfg.database.database
       ];
       ensureUsers = [{
-        name = cfg.database.username;
-        ensurePermissions = {
-          "DATABASE \"${cfg.database.database}\"" = "ALL PRIVILEGES";
-        };
+        name = cfg.user;
+        ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/dex.nix b/nixos/modules/services/web-apps/dex.nix
index bd041db007a1e..0c4a71c6dfe4b 100644
--- a/nixos/modules/services/web-apps/dex.nix
+++ b/nixos/modules/services/web-apps/dex.nix
@@ -108,8 +108,7 @@ in
         ProtectClock = true;
         ProtectHome = true;
         ProtectHostname = true;
-        # Would re-mount paths ignored by temporary root
-        #ProtectSystem = "strict";
+        ProtectSystem = "strict";
         ProtectControlGroups = true;
         ProtectKernelLogs = true;
         ProtectKernelModules = true;
@@ -121,9 +120,7 @@ in
         RestrictSUIDSGID = true;
         SystemCallArchitectures = "native";
         SystemCallFilter = [ "@system-service" "~@privileged @setuid @keyring" ];
-        TemporaryFileSystem = "/:ro";
-        # Does not work well with the temporary root
-        #UMask = "0066";
+        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 f80eb6b4c7f02..da1dba7d940ba 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -407,7 +407,7 @@ in
             type = with lib.types; nullOr (enum ["plain" "login" "cram_md5"]);
             default = null;
             description = lib.mdDoc ''
-              Authentication type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+              Authentication type to use, see https://api.rubyonrails.org/classes/ActionMailer/Base.html
             '';
           };
 
@@ -423,7 +423,7 @@ in
             type = lib.types.str;
             default = "peer";
             description = lib.mdDoc ''
-              How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html
+              How OpenSSL checks the certificate, see https://api.rubyonrails.org/classes/ActionMailer/Base.html
             '';
           };
 
diff --git a/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix
index f70da0829f446..6f88b3f3c6d2b 100644
--- a/nixos/modules/services/web-apps/documize.nix
+++ b/nixos/modules/services/web-apps/documize.nix
@@ -23,14 +23,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.documize-community;
-      defaultText = literalExpression "pkgs.documize-community";
-      description = lib.mdDoc ''
-        Which package to use for documize.
-      '';
-    };
+    package = mkPackageOption pkgs "documize-community" { };
 
     salt = mkOption {
       type = types.nullOr types.str;
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
index 9e9bfb1bfd839..256ab3229ea6c 100644
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -122,72 +122,13 @@ let
     };
   };
 
-  # The current implementations of `doRename`,  `mkRenamedOptionModule` do not provide the full options path when used with submodules.
-  # They would only show `settings.useacl' instead of `services.dokuwiki.sites."site1.local".settings.useacl'
-  # The partial re-implementation of these functions is done to help users in debugging by showing the full path.
-  mkRenamed = from: to: { config, options, name, ... }: let
-    pathPrefix = [ "services" "dokuwiki" "sites" name ];
-    fromPath = pathPrefix  ++ from;
-    fromOpt = getAttrFromPath from options;
-    toOp = getAttrsFromPath to config;
-    toPath = pathPrefix ++ to;
-  in {
-    options = setAttrByPath from (mkOption {
-      visible = false;
-      description = lib.mdDoc "Alias of {option}${showOption toPath}";
-      apply = x: builtins.trace "Obsolete option `${showOption fromPath}' is used. It was renamed to ${showOption toPath}" toOp;
-    });
-    config = mkMerge [
-      {
-        warnings = optional fromOpt.isDefined
-          "The option `${showOption fromPath}' defined in ${showFiles fromOpt.files} has been renamed to `${showOption toPath}'.";
-      }
-      (lib.modules.mkAliasAndWrapDefsWithPriority (setAttrByPath to) fromOpt)
-    ];
-  };
-
   siteOpts = { options, config, lib, name, ... }:
     {
-      imports = [
-        (mkRenamed [ "aclUse" ] [ "settings" "useacl" ])
-        (mkRenamed [ "superUser" ] [ "settings" "superuser" ])
-        (mkRenamed [ "disableActions" ] [ "settings"  "disableactions" ])
-        ({ config, options, ... }: let
-          showPath = suffix: lib.options.showOption ([ "services" "dokuwiki" "sites" name ] ++ suffix);
-          replaceExtraConfig = "Please use `${showPath ["settings"]}' to pass structured settings instead.";
-          ecOpt = options.extraConfig;
-          ecPath = showPath [ "extraConfig" ];
-        in {
-          options.extraConfig = mkOption {
-            visible = false;
-            apply = x: throw "The option ${ecPath} can no longer be used since it's been removed.\n${replaceExtraConfig}";
-          };
-          config.assertions = [
-            {
-              assertion = !ecOpt.isDefined;
-              message = "The option definition `${ecPath}' in ${showFiles ecOpt.files} no longer has any effect; please remove it.\n${replaceExtraConfig}";
-            }
-            {
-              assertion = config.mergedConfig.useacl -> (config.acl != null || config.aclFile != null);
-              message = "Either ${showPath [ "acl" ]} or ${showPath [ "aclFile" ]} is mandatory if ${showPath [ "settings" "useacl" ]} is true";
-            }
-            {
-              assertion = config.usersFile != null -> config.mergedConfig.useacl != false;
-              message = "${showPath [ "settings" "useacl" ]} is required when ${showPath [ "usersFile" ]} is set (Currently defined as `${config.usersFile}' in ${showFiles options.usersFile.files}).";
-            }
-          ];
-        })
-      ];
 
       options = {
         enable = mkEnableOption (lib.mdDoc "DokuWiki web application");
 
-        package = mkOption {
-          type = types.package;
-          default = pkgs.dokuwiki;
-          defaultText = literalExpression "pkgs.dokuwiki";
-          description = lib.mdDoc "Which DokuWiki package to use.";
-        };
+        package = mkPackageOption pkgs "dokuwiki" { };
 
         stateDir = mkOption {
           type = types.path;
@@ -335,14 +276,9 @@ let
           '';
         };
 
-        phpPackage = mkOption {
-          type = types.package;
-          relatedPackages = [ "php81" "php82" ];
-          default = pkgs.php81;
-          defaultText = "pkgs.php81";
-          description = lib.mdDoc ''
-            PHP package to use for this dokuwiki site.
-          '';
+        phpPackage = mkPackageOption pkgs "php" {
+          default = "php81";
+          example = "php82";
         };
 
         phpOptions = mkOption {
@@ -402,21 +338,6 @@ let
           '';
         };
 
-      # Required for the mkRenamedOptionModule
-      # TODO: Remove me once https://github.com/NixOS/nixpkgs/issues/96006 is fixed
-      # or we don't have any more notes about the removal of extraConfig, ...
-      warnings = mkOption {
-        type = types.listOf types.unspecified;
-        default = [ ];
-        visible = false;
-        internal = true;
-      };
-      assertions = mkOption {
-        type = types.listOf types.unspecified;
-        default = [ ];
-        visible = false;
-        internal = true;
-      };
     };
   };
 in
@@ -450,10 +371,6 @@ in
   # implementation
   config = mkIf (eachSite != {}) (mkMerge [{
 
-    warnings = flatten (mapAttrsToList (_: cfg: cfg.warnings) eachSite);
-
-    assertions = flatten (mapAttrsToList (_: cfg: cfg.assertions) eachSite);
-
     services.phpfpm.pools = mapAttrs' (hostName: cfg: (
       nameValuePair "dokuwiki-${hostName}" {
         inherit user;
diff --git a/nixos/modules/services/web-apps/dolibarr.nix b/nixos/modules/services/web-apps/dolibarr.nix
index 453229c130c22..193be47ab9b2d 100644
--- a/nixos/modules/services/web-apps/dolibarr.nix
+++ b/nixos/modules/services/web-apps/dolibarr.nix
@@ -1,8 +1,8 @@
 { config, pkgs, lib, ... }:
 let
-  inherit (lib) any boolToString concatStringsSep isBool isString mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption optionalAttrs types;
+  inherit (lib) any boolToString concatStringsSep isBool isString mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption optionalAttrs types mkPackageOption;
 
-  package = pkgs.dolibarr.override { inherit (cfg) stateDir; };
+  package = cfg.package.override { inherit (cfg) stateDir; };
 
   cfg = config.services.dolibarr;
   vhostCfg = lib.optionalAttrs (cfg.nginx != null) config.services.nginx.virtualHosts."${cfg.domain}";
@@ -50,6 +50,8 @@ in
   options.services.dolibarr = {
     enable = mkEnableOption (lib.mdDoc "dolibarr");
 
+    package = mkPackageOption pkgs "dolibarr" { };
+
     domain = mkOption {
       type = types.str;
       default = "localhost";
diff --git a/nixos/modules/services/web-apps/engelsystem.nix b/nixos/modules/services/web-apps/engelsystem.nix
index 138e2f3f1b90b..669620debce55 100644
--- a/nixos/modules/services/web-apps/engelsystem.nix
+++ b/nixos/modules/services/web-apps/engelsystem.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, utils, ... }:
 
 let
-  inherit (lib) mkDefault mkEnableOption mkIf mkOption types literalExpression;
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption types mkPackageOption;
   cfg = config.services.engelsystem;
 in {
   options = {
@@ -22,12 +22,7 @@ in {
         description = lib.mdDoc "Domain to serve on.";
       };
 
-      package = mkOption {
-        type = types.package;
-        description = lib.mdDoc "Engelsystem package used for the service.";
-        default = pkgs.engelsystem;
-        defaultText = literalExpression "pkgs.engelsystem";
-      };
+      package = mkPackageOption pkgs "engelsystem" { };
 
       createDatabase = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/web-apps/ethercalc.nix b/nixos/modules/services/web-apps/ethercalc.nix
index a5be86a34aa6d..a38e89ec0de96 100644
--- a/nixos/modules/services/web-apps/ethercalc.nix
+++ b/nixos/modules/services/web-apps/ethercalc.nix
@@ -24,12 +24,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        default = pkgs.ethercalc;
-        defaultText = literalExpression "pkgs.ethercalc";
-        type = types.package;
-        description = lib.mdDoc "Ethercalc package to use.";
-      };
+      package = mkPackageOption pkgs "ethercalc" { };
 
       host = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/web-apps/fluidd.nix b/nixos/modules/services/web-apps/fluidd.nix
index d4b86b9dfb39a..1d9b56f5ccf20 100644
--- a/nixos/modules/services/web-apps/fluidd.nix
+++ b/nixos/modules/services/web-apps/fluidd.nix
@@ -8,12 +8,7 @@ in
   options.services.fluidd = {
     enable = mkEnableOption (lib.mdDoc "Fluidd, a Klipper web interface for managing your 3d printer");
 
-    package = mkOption {
-      type = types.package;
-      description = lib.mdDoc "Fluidd package to be used in the module";
-      default = pkgs.fluidd;
-      defaultText = literalExpression "pkgs.fluidd";
-    };
+    package = mkPackageOption pkgs "fluidd" { };
 
     hostName = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix
index ffc05d0e41f87..c8399143c37ba 100644
--- a/nixos/modules/services/web-apps/freshrss.nix
+++ b/nixos/modules/services/web-apps/freshrss.nix
@@ -12,12 +12,7 @@ in
   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.";
-    };
+    package = mkPackageOption pkgs "freshrss" { };
 
     defaultUser = mkOption {
       type = types.str;
@@ -220,7 +215,7 @@ in
             "catch_workers_output" = true;
           };
           phpEnv = {
-            FRESHRSS_DATA_PATH = "${cfg.dataDir}";
+            DATA_PATH = "${cfg.dataDir}";
           };
         };
       };
@@ -267,7 +262,7 @@ in
             WorkingDirectory = cfg.package;
           };
           environment = {
-            FRESHRSS_DATA_PATH = cfg.dataDir;
+            DATA_PATH = cfg.dataDir;
           };
 
           script =
@@ -299,10 +294,9 @@ in
       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;
+          DATA_PATH = cfg.dataDir;
         };
         serviceConfig = defaultServiceConfig //{
           ExecStart = "${cfg.package}/app/actualize_script.php";
diff --git a/nixos/modules/services/web-apps/galene.nix b/nixos/modules/services/web-apps/galene.nix
index 747b85f94c65a..28d4069ec3854 100644
--- a/nixos/modules/services/web-apps/galene.nix
+++ b/nixos/modules/services/web-apps/galene.nix
@@ -110,14 +110,7 @@ in
         description = lib.mdDoc "Web server directory.";
       };
 
-      package = mkOption {
-        default = pkgs.galene;
-        defaultText = literalExpression "pkgs.galene";
-        type = types.package;
-        description = lib.mdDoc ''
-          Package for running Galene.
-        '';
-      };
+      package = mkPackageOption pkgs "galene" { };
     };
   };
 
@@ -186,7 +179,7 @@ in
           ProtectSystem = "strict";
           ReadWritePaths = cfg.recordingsDir;
           RemoveIPC = true;
-          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" ];
           RestrictNamespaces = true;
           RestrictRealtime = true;
           RestrictSUIDSGID = true;
diff --git a/nixos/modules/services/web-apps/gerrit.nix b/nixos/modules/services/web-apps/gerrit.nix
index ab2eeea09bdc3..5c62a7ebbd932 100644
--- a/nixos/modules/services/web-apps/gerrit.nix
+++ b/nixos/modules/services/web-apps/gerrit.nix
@@ -61,19 +61,9 @@ in
     services.gerrit = {
       enable = mkEnableOption (lib.mdDoc "Gerrit service");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.gerrit;
-        defaultText = literalExpression "pkgs.gerrit";
-        description = lib.mdDoc "Gerrit package to use";
-      };
+      package = mkPackageOption pkgs "gerrit" { };
 
-      jvmPackage = mkOption {
-        type = types.package;
-        default = pkgs.jre_headless;
-        defaultText = literalExpression "pkgs.jre_headless";
-        description = lib.mdDoc "Java Runtime Environment package to use";
-      };
+      jvmPackage = mkPackageOption pkgs "jre_headless" { };
 
       jvmOpts = mkOption {
         type = types.listOf types.str;
diff --git a/nixos/modules/services/web-apps/gotosocial.nix b/nixos/modules/services/web-apps/gotosocial.nix
index f7ae018d5b7cc..45464f646da89 100644
--- a/nixos/modules/services/web-apps/gotosocial.nix
+++ b/nixos/modules/services/web-apps/gotosocial.nix
@@ -32,7 +32,7 @@ in
   options.services.gotosocial = {
     enable = lib.mkEnableOption (lib.mdDoc "ActivityPub social network server");
 
-    package = lib.mkPackageOptionMD pkgs "gotosocial" { };
+    package = lib.mkPackageOption pkgs "gotosocial" { };
 
     openFirewall = lib.mkOption {
       type = lib.types.bool;
@@ -128,9 +128,7 @@ in
       ensureUsers = [
         {
           name = "gotosocial";
-          ensurePermissions = {
-            "DATABASE gotosocial" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/grocy.nix b/nixos/modules/services/web-apps/grocy.nix
index b0d3c44cea81c..858fd74279d0f 100644
--- a/nixos/modules/services/web-apps/grocy.nix
+++ b/nixos/modules/services/web-apps/grocy.nix
@@ -8,6 +8,8 @@ in {
   options.services.grocy = {
     enable = mkEnableOption (lib.mdDoc "grocy");
 
+    package = mkPackageOption pkgs "grocy" { };
+
     hostName = mkOption {
       type = types.str;
       description = lib.mdDoc ''
@@ -115,9 +117,9 @@ in {
       user = "grocy";
       group = "nginx";
 
-      # PHP 8.1 is the only version which is supported/tested by upstream:
-      # https://github.com/grocy/grocy/blob/v4.0.0/README.md#platform-support
-      phpPackage = pkgs.php81;
+      # PHP 8.1 and 8.2 are the only version which are supported/tested by upstream:
+      # https://github.com/grocy/grocy/blob/v4.0.2/README.md#platform-support
+      phpPackage = pkgs.php82;
 
       inherit (cfg.phpfpm) settings;
 
@@ -130,10 +132,20 @@ in {
       };
     };
 
+    # After an update of grocy, the viewcache needs to be deleted. Otherwise grocy will not work
+    # https://github.com/grocy/grocy#how-to-update
+    systemd.services.grocy-setup = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "phpfpm-grocy.service" ];
+      script = ''
+        rm -rf ${cfg.dataDir}/viewcache/*
+      '';
+    };
+
     services.nginx = {
       enable = true;
       virtualHosts."${cfg.hostName}" = mkMerge [
-        { root = "${pkgs.grocy}/public";
+        { root = "${cfg.package}/public";
           locations."/".extraConfig = ''
             rewrite ^ /index.php;
           '';
diff --git a/nixos/modules/services/web-apps/guacamole-client.nix b/nixos/modules/services/web-apps/guacamole-client.nix
index c12f6582468c9..04d867c0a9431 100644
--- a/nixos/modules/services/web-apps/guacamole-client.nix
+++ b/nixos/modules/services/web-apps/guacamole-client.nix
@@ -11,7 +11,7 @@ in
   options = {
     services.guacamole-client = {
       enable = lib.mkEnableOption (lib.mdDoc "Apache Guacamole Client (Tomcat)");
-      package = lib.mkPackageOptionMD pkgs "guacamole-client" { };
+      package = lib.mkPackageOption pkgs "guacamole-client" { };
 
       settings = lib.mkOption {
         type = lib.types.submodule {
diff --git a/nixos/modules/services/web-apps/guacamole-server.nix b/nixos/modules/services/web-apps/guacamole-server.nix
index 0cffdce83d83f..71e80d8aad325 100644
--- a/nixos/modules/services/web-apps/guacamole-server.nix
+++ b/nixos/modules/services/web-apps/guacamole-server.nix
@@ -10,7 +10,7 @@ in
   options = {
     services.guacamole-server = {
       enable = lib.mkEnableOption (lib.mdDoc "Apache Guacamole Server (guacd)");
-      package = lib.mkPackageOptionMD pkgs "guacamole-server" { };
+      package = lib.mkPackageOption pkgs "guacamole-server" { };
 
       extraEnvironment = lib.mkOption {
         type = lib.types.attrsOf lib.types.str;
diff --git a/nixos/modules/services/web-apps/healthchecks.nix b/nixos/modules/services/web-apps/healthchecks.nix
index b3fdb681e2f30..1d439f162313b 100644
--- a/nixos/modules/services/web-apps/healthchecks.nix
+++ b/nixos/modules/services/web-apps/healthchecks.nix
@@ -1,16 +1,16 @@
-{ config, lib, pkgs, buildEnv, ... }:
+{ config, lib, options, pkgs, buildEnv, ... }:
 
 with lib;
 
 let
   defaultUser = "healthchecks";
   cfg = config.services.healthchecks;
+  opt = options.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);
@@ -33,12 +33,7 @@ in
       '';
     };
 
-    package = mkOption {
-      default = pkgs.healthchecks;
-      defaultText = literalExpression "pkgs.healthchecks";
-      type = types.package;
-      description = lib.mdDoc "healthchecks package to use.";
-    };
+    package = mkPackageOption pkgs "healthchecks" { };
 
     user = mkOption {
       default = defaultUser;
@@ -98,17 +93,24 @@ in
       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
+        Settings which are explicitly covered in options below, 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.
+        We add additional 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.
+        - `_FILE` variants for several values that hold sensitive information in
+          [Healthchecks configuration](https://healthchecks.io/docs/self_hosted_configuration/) so
+          that they also can be read from a file and kept out of /nix/store. To see which values
+          have support for a `_FILE` variant, run:
+          - `nix-instantiate --eval --expr '(import <nixpkgs> {}).healthchecks.secrets'`
+          - or `nix eval 'nixpkgs#healthchecks.secrets'` if the flake support has been enabled.
       '';
-      type = types.submodule {
+      type = types.submodule (settings: {
         freeformType = types.attrsOf types.str;
         options = {
           ALLOWED_HOSTS = lib.mkOption {
@@ -143,8 +145,28 @@ in
             '';
             apply = boolToPython;
           };
+
+          DB = mkOption {
+            type = types.enum [ "sqlite" "postgres" "mysql" ];
+            default = "sqlite";
+            description = lib.mdDoc "Database engine to use.";
+          };
+
+          DB_NAME = mkOption {
+            type = types.str;
+            default =
+              if settings.config.DB == "sqlite"
+              then "${cfg.dataDir}/healthchecks.sqlite"
+              else "hc";
+            defaultText = lib.literalExpression ''
+              if config.${settings.options.DB} == "sqlite"
+              then "''${config.${opt.dataDir}}/healthchecks.sqlite"
+              else "hc"
+            '';
+            description = lib.mdDoc "Database name.";
+          };
         };
-      };
+      });
     };
   };
 
@@ -154,6 +176,7 @@ in
     systemd.targets.healthchecks = {
       description = "Target for all Healthchecks services";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network.target" "network-online.target" ];
     };
 
@@ -168,7 +191,7 @@ in
           StateDirectoryMode = mkIf (cfg.dataDir == "/var/lib/healthchecks") "0750";
         };
       in
-        {
+      {
         healthchecks-migration = {
           description = "Healthchecks migrations";
           wantedBy = [ "healthchecks.target" ];
diff --git a/nixos/modules/services/web-apps/hedgedoc.nix b/nixos/modules/services/web-apps/hedgedoc.nix
index bfa5fd5aff25f..adcfe80a7332b 100644
--- a/nixos/modules/services/web-apps/hedgedoc.nix
+++ b/nixos/modules/services/web-apps/hedgedoc.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) literalExpression mdDoc mkEnableOption mkIf mkOption mkPackageOptionMD mkRenamedOptionModule types versionAtLeast;
+  inherit (lib) mkOption types mdDoc literalExpression;
 
   cfg = config.services.hedgedoc;
 
@@ -9,990 +9,189 @@ let
   # versionAtLeast statement remains set to 21.03 for backwards compatibility.
   # See https://github.com/NixOS/nixpkgs/pull/108899 and
   # https://github.com/NixOS/rfcs/blob/master/rfcs/0080-nixos-release-schedule.md.
-  name = if versionAtLeast config.system.stateVersion "21.03"
-    then "hedgedoc"
-    else "codimd";
+  name = if lib.versionAtLeast config.system.stateVersion "21.03" then
+    "hedgedoc"
+  else
+    "codimd";
 
-  settingsFormat = pkgs.formats.json {};
-
-  prettyJSON = conf:
-    pkgs.runCommandLocal "hedgedoc-config.json" {
-      nativeBuildInputs = [ pkgs.jq ];
-    } ''
-      jq '{production:del(.[]|nulls)|del(.[][]?|nulls)}' \
-        < ${settingsFormat.generate "hedgedoc-ugly.json" cfg.settings} \
-        > $out
-    '';
+  settingsFormat = pkgs.formats.json { };
 in
 {
+  meta.maintainers = with lib.maintainers; [ SuperSandro2000 h7x4 ];
+
   imports = [
-    (mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
-    (mkRenamedOptionModule
-      [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
+    (lib.mkRenamedOptionModule [ "services" "codimd" ] [ "services" "hedgedoc" ])
+    (lib.mkRenamedOptionModule [ "services" "hedgedoc" "configuration" ] [ "services" "hedgedoc" "settings" ])
+    (lib.mkRenamedOptionModule [ "services" "hedgedoc" "groups" ] [ "users" "users" "hedgedoc" "extraGroups" ])
+    (lib.mkRemovedOptionModule [ "services" "hedgedoc" "workDir" ] ''
+      This option has been removed in favor of systemd managing the state directory.
+
+      If you have set this option without specifying `services.settings.uploadsDir`,
+      please move these files to `/var/lib/hedgedoc/uploads`, or set the option to point
+      at the correct location.
+    '')
   ];
 
   options.services.hedgedoc = {
-    package = mkPackageOptionMD pkgs "hedgedoc" { };
-    enable = mkEnableOption (lib.mdDoc "the HedgeDoc Markdown Editor");
+    package = lib.mkPackageOption pkgs "hedgedoc" { };
+    enable = lib.mkEnableOption (mdDoc "the HedgeDoc Markdown Editor");
 
-    groups = mkOption {
-      type = types.listOf types.str;
-      default = [];
-      description = lib.mdDoc ''
-        Groups to which the service user should be added.
-      '';
-    };
-
-    workDir = mkOption {
-      type = types.path;
-      default = "/var/lib/${name}";
-      description = lib.mdDoc ''
-        Working directory for the HedgeDoc service.
-      '';
-    };
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          domain = mkOption {
+            type = with types; nullOr str;
+            default = null;
+            example = "hedgedoc.org";
+            description = mdDoc ''
+              Domain to use for website.
 
-    settings = let options = {
-      debug = mkEnableOption (lib.mdDoc "debug mode");
-      domain = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "hedgedoc.org";
-        description = lib.mdDoc ''
-          Domain name for the HedgeDoc instance.
-        '';
-      };
-      urlPath = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/url/path/to/hedgedoc";
-        description = lib.mdDoc ''
-          Path under which HedgeDoc is accessible.
-        '';
-      };
-      host = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = lib.mdDoc ''
-          Address to listen on.
-        '';
-      };
-      port = mkOption {
-        type = types.port;
-        default = 3000;
-        example = 80;
-        description = lib.mdDoc ''
-          Port to listen on.
-        '';
-      };
-      path = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "/run/hedgedoc.sock";
-        description = lib.mdDoc ''
-          Specify where a UNIX domain socket should be placed.
-        '';
-      };
-      allowOrigin = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "localhost" "hedgedoc.org" ];
-        description = lib.mdDoc ''
-          List of domains to whitelist.
-        '';
-      };
-      useSSL = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable to use SSL server. This will also enable
-          {option}`protocolUseSSL`.
-        '';
-      };
-      enableStatsApi = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enables or disables the /status and /metrics endpoint.
-        '';
-      };
-      hsts = {
-        enable = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to enable HSTS if HTTPS is also enabled.
-          '';
-        };
-        maxAgeSeconds = mkOption {
-          type = types.int;
-          default = 31536000;
-          description = lib.mdDoc ''
-            Max duration for clients to keep the HSTS status.
-          '';
-        };
-        includeSubdomains = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to include subdomains in HSTS.
-          '';
-        };
-        preload = mkOption {
-          type = types.bool;
-          default = true;
-          description = lib.mdDoc ''
-            Whether to allow preloading of the site's HSTS status.
-          '';
-        };
-      };
-      csp = mkOption {
-        type = types.nullOr types.attrs;
-        default = null;
-        example = literalExpression ''
-          {
-            enable = true;
-            directives = {
-              scriptSrc = "trustworthy.scripts.example.com";
-            };
-            upgradeInsecureRequest = "auto";
-            addDefaults = true;
-          }
-        '';
-        description = lib.mdDoc ''
-          Specify the Content Security Policy which is passed to Helmet.
-          For configuration details see <https://helmetjs.github.io/docs/csp/>.
-        '';
-      };
-      protocolUseSSL = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable to use TLS for resource paths.
-          This only applies when {option}`domain` is set.
-        '';
-      };
-      urlAddPort = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc ''
-          Enable to add the port to callback URLs.
-          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 = lib.mdDoc ''
-          Whether to use CDN resources or not.
-        '';
-      };
-      allowAnonymous = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to allow anonymous usage.
-        '';
-      };
-      allowAnonymousEdits = mkOption {
-        type = types.bool;
-        default = false;
-        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 = 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 = lib.mdDoc ''
-          Default permissions for notes.
-          This only applies for signed-in users.
-        '';
-      };
-      dbURL = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = ''
-          postgres://user:pass@host:5432/dbname
-        '';
-        description = lib.mdDoc ''
-          Specify which database to use.
-          HedgeDoc supports mysql, postgres, sqlite and mssql.
-          See [
-          https://sequelize.readthedocs.io/en/v3/](https://sequelize.readthedocs.io/en/v3/) for more information.
-          Note: This option overrides {option}`db`.
-        '';
-      };
-      db = mkOption {
-        type = types.attrs;
-        default = {};
-        example = literalExpression ''
-          {
-            dialect = "sqlite";
-            storage = "/var/lib/${name}/db.${name}.sqlite";
-          }
-        '';
-        description = lib.mdDoc ''
-          Specify the configuration for sequelize.
-          HedgeDoc supports mysql, postgres, sqlite and mssql.
-          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 = 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 = 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 = 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 = lib.mdDoc ''
-          Path to the SSL dh params. Needed when {option}`useSSL` is enabled.
-        '';
-      };
-      tmpPath = mkOption {
-        type = types.str;
-        default = "/tmp";
-        description = lib.mdDoc ''
-          Path to the temp directory HedgeDoc should use.
-          Note that {option}`serviceConfig.PrivateTmp` is enabled for
-          the HedgeDoc systemd service by default.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      defaultNotePath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/default.md";
-        defaultText = literalExpression "\"\${cfg.package}/public/default.md\"";
-        description = lib.mdDoc ''
-          Path to the default Note file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      docsPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/docs";
-        defaultText = literalExpression "\"\${cfg.package}/public/docs\"";
-        description = lib.mdDoc ''
-          Path to the docs directory.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      indexPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/index.ejs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/index.ejs\"";
-        description = lib.mdDoc ''
-          Path to the index template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      hackmdPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/hackmd.ejs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/hackmd.ejs\"";
-        description = lib.mdDoc ''
-          Path to the hackmd template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      errorPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/error.ejs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/error.ejs\"";
-        description = lib.mdDoc ''
-          Path to the error template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      prettyPath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/pretty.ejs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/pretty.ejs\"";
-        description = lib.mdDoc ''
-          Path to the pretty template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      slidePath = mkOption {
-        type = types.nullOr types.str;
-        default = "${cfg.package}/public/views/slide.hbs";
-        defaultText = literalExpression "\"\${cfg.package}/public/views/slide.hbs\"";
-        description = lib.mdDoc ''
-          Path to the slide template file.
-          (Non-canonical paths are relative to HedgeDoc's base directory)
-        '';
-      };
-      uploadsPath = mkOption {
-        type = types.str;
-        default = "${cfg.workDir}/uploads";
-        defaultText = literalExpression "\"\${cfg.workDir}/uploads\"";
-        description = lib.mdDoc ''
-          Path under which uploaded files are saved.
-        '';
-      };
-      sessionName = mkOption {
-        type = types.str;
-        default = "connect.sid";
-        description = lib.mdDoc ''
-          Specify the name of the session cookie.
-        '';
-      };
-      sessionSecret = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc ''
-          Specify the secret used to sign the session cookie.
-          If unset, one will be generated on startup.
-        '';
-      };
-      sessionLife = mkOption {
-        type = types.int;
-        default = 1209600000;
-        description = lib.mdDoc ''
-          Session life time in milliseconds.
-        '';
-      };
-      heartbeatInterval = mkOption {
-        type = types.int;
-        default = 5000;
-        description = lib.mdDoc ''
-          Specify the socket.io heartbeat interval.
-        '';
-      };
-      heartbeatTimeout = mkOption {
-        type = types.int;
-        default = 10000;
-        description = lib.mdDoc ''
-          Specify the socket.io heartbeat timeout.
-        '';
-      };
-      documentMaxLength = mkOption {
-        type = types.int;
-        default = 100000;
-        description = lib.mdDoc ''
-          Specify the maximum document length.
-        '';
-      };
-      email = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to enable email sign-in.
-        '';
-      };
-      allowEmailRegister = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to enable email registration.
-        '';
-      };
-      allowGravatar = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to use gravatar as profile picture source.
-        '';
-      };
-      imageUploadType = mkOption {
-        type = types.enum [ "imgur" "s3" "minio" "filesystem" ];
-        default = "filesystem";
-        description = lib.mdDoc ''
-          Specify where to upload images.
-        '';
-      };
-      minio = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            accessKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Minio access key.
-              '';
-            };
-            secretKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Minio secret key.
-              '';
-            };
-            endPoint = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Minio endpoint.
-              '';
-            };
-            port = mkOption {
-              type = types.port;
-              default = 9000;
-              description = lib.mdDoc ''
-                Minio listen port.
-              '';
-            };
-            secure = mkOption {
-              type = types.bool;
-              default = true;
-              description = lib.mdDoc ''
-                Whether to use HTTPS for Minio.
-              '';
-            };
+              This is useful if you are trying to run hedgedoc behind
+              a reverse proxy.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the minio third-party integration.";
-      };
-      s3 = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            accessKeyId = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                AWS access key id.
-              '';
-            };
-            secretAccessKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                AWS access key.
-              '';
-            };
-            region = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                AWS S3 region.
-              '';
-            };
-          };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the s3 third-party integration.";
-      };
-      s3bucket = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc ''
-          Specify the bucket name for upload types `s3` and `minio`.
-        '';
-      };
-      allowPDFExport = mkOption {
-        type = types.bool;
-        default = true;
-        description = lib.mdDoc ''
-          Whether to enable PDF exports.
-        '';
-      };
-      imgur.clientId = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = lib.mdDoc ''
-          Imgur API client ID.
-        '';
-      };
-      azure = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            connectionString = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Azure Blob Storage connection string.
-              '';
-            };
-            container = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Azure Blob Storage container name.
-                It will be created if non-existent.
-              '';
-            };
-          };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the azure third-party integration.";
-      };
-      oauth2 = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            authorizationURL = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Specify the OAuth authorization URL.
-              '';
-            };
-            tokenURL = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Specify the OAuth token URL.
-              '';
-            };
-            baseURL = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the OAuth base URL.
-              '';
-            };
-            userProfileURL = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the OAuth userprofile URL.
-              '';
-            };
-            userProfileUsernameAttr = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              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 = 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 = lib.mdDoc ''
-                Specify the name of the attribute for the email from the claim.
-              '';
-            };
-            scope = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the OAuth scope.
-              '';
-            };
-            providerName = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the name to be displayed for this strategy.
-              '';
-            };
-            rolesClaim = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the role claim name.
-              '';
-            };
-            accessRole = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify role which should be included in the ID token roles claim to grant access
-              '';
-            };
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Specify the OAuth client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = with types; nullOr str;
-              default = null;
-              description = lib.mdDoc ''
-                Specify the OAuth client secret.
-              '';
-            };
+          urlPath = mkOption {
+            type = with types; nullOr str;
+            default = null;
+            example = "hedgedoc";
+            description = mdDoc ''
+              URL path for the website.
+
+              This is useful if you are hosting hedgedoc on a path like
+              `www.example.com/hedgedoc`
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the OAuth integration.";
-      };
-      facebook = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Facebook API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Facebook API client secret.
-              '';
-            };
+          host = mkOption {
+            type = with types; nullOr str;
+            default = "localhost";
+            description = mdDoc ''
+              Address to listen on.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the facebook third-party integration";
-      };
-      twitter = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            consumerKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Twitter API consumer key.
-              '';
-            };
-            consumerSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Twitter API consumer secret.
-              '';
-            };
+          port = mkOption {
+            type = types.port;
+            default = 3000;
+            example = 80;
+            description = mdDoc ''
+              Port to listen on.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the Twitter third-party integration.";
-      };
-      github = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                GitHub API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Github API client secret.
-              '';
-            };
+          path = mkOption {
+            type = with types; nullOr path;
+            default = null;
+            example = "/run/hedgedoc/hedgedoc.sock";
+            description = mdDoc ''
+              Path to UNIX domain socket to listen on
+
+              ::: {.note}
+                If specified, {option}`host` and {option}`port` will be ignored.
+              :::
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the GitHub third-party integration.";
-      };
-      gitlab = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            baseURL = mkOption {
-              type = types.str;
-              default = "";
-              description = lib.mdDoc ''
-                GitLab API authentication endpoint.
-                Only needed for other endpoints than gitlab.com.
-              '';
-            };
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                GitLab API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                GitLab API client secret.
-              '';
-            };
-            scope = mkOption {
-              type = types.enum [ "api" "read_user" ];
-              default = "api";
-              description = lib.mdDoc ''
-                GitLab API requested scope.
-                GitLab snippet import/export requires api scope.
-              '';
-            };
+          protocolUseSSL = mkOption {
+            type = types.bool;
+            default = false;
+            example = true;
+            description = mdDoc ''
+              Use `https://` for all links.
+
+              This is useful if you are trying to run hedgedoc behind
+              a reverse proxy.
+
+              ::: {.note}
+                Only applied if {option}`domain` is set.
+              :::
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the GitLab third-party integration.";
-      };
-      mattermost = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            baseURL = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Mattermost authentication endpoint.
-              '';
-            };
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Mattermost API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Mattermost API client secret.
-              '';
-            };
+          allowOrigin = mkOption {
+            type = with types; listOf str;
+            default = with cfg.settings; [ host ] ++ lib.optionals (domain != null) [ domain ];
+            defaultText = literalExpression ''
+              with config.services.hedgedoc.settings; [ host ] ++ lib.optionals (domain != null) [ domain ]
+            '';
+            example = [ "localhost" "hedgedoc.org" ];
+            description = mdDoc ''
+              List of domains to whitelist.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the Mattermost third-party integration.";
-      };
-      dropbox = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Dropbox API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Dropbox API client secret.
-              '';
-            };
-            appKey = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Dropbox app key.
-              '';
-            };
+          db = mkOption {
+            type = types.attrs;
+            default = {
+              dialect = "sqlite";
+              storage = "/var/lib/${name}/db.sqlite";
+            };
+            defaultText = literalExpression ''
+              {
+                dialect = "sqlite";
+                storage = "/var/lib/hedgedoc/db.sqlite";
+              }
+            '';
+            example = literalExpression ''
+              db = {
+                username = "hedgedoc";
+                database = "hedgedoc";
+                host = "localhost:5432";
+                # or via socket
+                # host = "/run/postgresql";
+                dialect = "postgresql";
+              };
+            '';
+            description = mdDoc ''
+              Specify the configuration for sequelize.
+              HedgeDoc supports `mysql`, `postgres`, `sqlite` and `mssql`.
+              See <https://sequelize.readthedocs.io/en/v3/>
+              for more information.
+
+              ::: {.note}
+                The relevant parts will be overriden if you set {option}`dbURL`.
+              :::
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the Dropbox third-party integration.";
-      };
-      google = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            clientID = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Google API client ID.
-              '';
-            };
-            clientSecret = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Google API client secret.
-              '';
-            };
+          useSSL = mkOption {
+            type = types.bool;
+            default = false;
+            description = mdDoc ''
+              Enable to use SSL server.
+
+              ::: {.note}
+                This will also enable {option}`protocolUseSSL`.
+
+                It will also require you to set the following:
+
+                - {option}`sslKeyPath`
+                - {option}`sslCertPath`
+                - {option}`sslCAPath`
+                - {option}`dhParamPath`
+              :::
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the Google third-party integration.";
-      };
-      ldap = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            providerName = mkOption {
-              type = types.str;
-              default = "";
-              description = lib.mdDoc ''
-                Optional name to be displayed at login form, indicating the LDAP provider.
-              '';
-            };
-            url = mkOption {
-              type = types.str;
-              example = "ldap://localhost";
-              description = lib.mdDoc ''
-                URL of LDAP server.
-              '';
-            };
-            bindDn = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Bind DN for LDAP access.
-              '';
-            };
-            bindCredentials = mkOption {
-              type = types.str;
-              description = lib.mdDoc ''
-                Bind credentials for LDAP access.
-              '';
-            };
-            searchBase = mkOption {
-              type = types.str;
-              example = "o=users,dc=example,dc=com";
-              description = lib.mdDoc ''
-                LDAP directory to begin search from.
-              '';
-            };
-            searchFilter = mkOption {
-              type = types.str;
-              example = "(uid={{username}})";
-              description = lib.mdDoc ''
-                LDAP filter to search with.
-              '';
-            };
-            searchAttributes = mkOption {
-              type = types.nullOr (types.listOf types.str);
-              default = null;
-              example = [ "displayName" "mail" ];
-              description = lib.mdDoc ''
-                LDAP attributes to search with.
-              '';
-            };
-            userNameField = mkOption {
-              type = types.str;
-              default = "";
-              description = lib.mdDoc ''
-                LDAP field which is used as the username on HedgeDoc.
-                By default {option}`useridField` is used.
-              '';
-            };
-            useridField = mkOption {
-              type = types.str;
-              example = "uid";
-              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 = lib.mdDoc ''
-                Root CA for LDAP TLS in PEM format.
-              '';
-            };
+          uploadsPath = mkOption {
+            type = types.path;
+            default = "/var/lib/${name}/uploads";
+            defaultText = "/var/lib/hedgedoc/uploads";
+            description = mdDoc ''
+              Directory for storing uploaded images.
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the LDAP integration.";
-      };
-      saml = mkOption {
-        type = types.nullOr (types.submodule {
-          options = {
-            idpSsoUrl = mkOption {
-              type = types.str;
-              example = "https://idp.example.com/sso";
-              description = lib.mdDoc ''
-                IdP authentication endpoint.
-              '';
-            };
-            idpCert = mkOption {
-              type = types.path;
-              example = "/path/to/cert.pem";
-              description = lib.mdDoc ''
-                Path to IdP certificate file in PEM format.
-              '';
-            };
-            issuer = mkOption {
-              type = types.str;
-              default = "";
-              description = lib.mdDoc ''
-                Optional identity of the service provider.
-                This defaults to the server URL.
-              '';
-            };
-            identifierFormat = mkOption {
-              type = types.str;
-              default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
-              description = lib.mdDoc ''
-                Optional name identifier format.
-              '';
-            };
-            groupAttribute = mkOption {
-              type = types.str;
-              default = "";
-              example = "memberOf";
-              description = lib.mdDoc ''
-                Optional attribute name for group list.
-              '';
-            };
-            externalGroups = mkOption {
-              type = types.listOf types.str;
-              default = [];
-              example = [ "Temporary-staff" "External-users" ];
-              description = lib.mdDoc ''
-                Excluded group names.
-              '';
-            };
-            requiredGroups = mkOption {
-              type = types.listOf types.str;
-              default = [];
-              example = [ "Hedgedoc-Users" ];
-              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 = lib.mdDoc ''
-                  Attribute map for `id`.
-                  Defaults to `NameID` of SAML response.
-                '';
-              };
-              username = mkOption {
-                type = types.str;
-                default = "";
-                description = lib.mdDoc ''
-                  Attribute map for `username`.
-                  Defaults to `NameID` of SAML response.
-                '';
-              };
-              email = mkOption {
-                type = types.str;
-                default = "";
-                description = lib.mdDoc ''
-                  Attribute map for `email`.
-                  Defaults to `NameID` of SAML response if
-                  {option}`identifierFormat` has
-                  the default value.
-                '';
-              };
-            };
+
+          # Declared because we change the default to false.
+          allowGravatar = mkOption {
+            type = types.bool;
+            default = false;
+            example = true;
+            description = mdDoc ''
+              Whether to enable [Libravatar](https://wiki.libravatar.org/) as
+              profile picture source on your instance.
+
+              Despite the naming of the setting, Hedgedoc replaced Gravatar
+              with Libravatar in [CodiMD 1.4.0](https://hedgedoc.org/releases/1.4.0/)
+            '';
           };
-        });
-        default = null;
-        description = lib.mdDoc "Configure the SAML integration.";
-      };
-    }; in lib.mkOption {
-      type = lib.types.submodule {
-        freeformType = settingsFormat.type;
-        inherit options;
+        };
       };
-      description = lib.mdDoc ''
+
+      description = mdDoc ''
         HedgeDoc configuration, see
         <https://docs.hedgedoc.org/configuration/>
         for documentation.
@@ -1003,7 +202,7 @@ in
       type = with types; nullOr path;
       default = null;
       example = "/var/lib/hedgedoc/hedgedoc.env";
-      description = lib.mdDoc ''
+      description = mdDoc ''
         Environment file as defined in {manpage}`systemd.exec(5)`.
 
         Secrets may be passed to the service without adding them to the world-readable
@@ -1028,45 +227,94 @@ in
     };
   };
 
-  config = mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.settings.db == {} -> (
-          cfg.settings.dbURL != "" && cfg.settings.dbURL != null
-        );
-        message = "Database configuration for HedgeDoc missing."; }
-    ];
-    users.groups.${name} = {};
+  config = lib.mkIf cfg.enable {
+    users.groups.${name} = { };
     users.users.${name} = {
       description = "HedgeDoc service user";
       group = name;
-      extraGroups = cfg.groups;
-      home = cfg.workDir;
-      createHome = true;
       isSystemUser = true;
     };
 
+    services.hedgedoc.settings = {
+      defaultNotePath = lib.mkDefault "${cfg.package}/public/default.md";
+      docsPath = lib.mkDefault "${cfg.package}/public/docs";
+      viewPath = lib.mkDefault "${cfg.package}/public/views";
+    };
+
     systemd.services.hedgedoc = {
       description = "HedgeDoc Service";
+      documentation = [ "https://docs.hedgedoc.org/" ];
       wantedBy = [ "multi-user.target" ];
       after = [ "networking.target" ];
-      preStart = ''
-        ${pkgs.envsubst}/bin/envsubst \
-          -o ${cfg.workDir}/config.json \
-          -i ${prettyJSON cfg.settings}
-        mkdir -p ${cfg.settings.uploadsPath}
-      '';
+      preStart =
+        let
+          configFile = settingsFormat.generate "hedgedoc-config.json" {
+            production = cfg.settings;
+          };
+        in
+        ''
+          ${pkgs.envsubst}/bin/envsubst \
+            -o /run/${name}/config.json \
+            -i ${configFile}
+          ${pkgs.coreutils}/bin/mkdir -p ${cfg.settings.uploadsPath}
+        '';
       serviceConfig = {
-        WorkingDirectory = cfg.workDir;
-        StateDirectory = [ cfg.workDir cfg.settings.uploadsPath ];
-        ExecStart = "${lib.getExe cfg.package}";
-        EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
+        User = name;
+        Group = name;
+
+        Restart = "always";
+        ExecStart = "${cfg.package}/bin/hedgedoc";
+        RuntimeDirectory = [ name ];
+        StateDirectory = [ name ];
+        WorkingDirectory = "/run/${name}";
+        ReadWritePaths = [
+          "-${cfg.settings.uploadsPath}"
+        ] ++ lib.optionals (cfg.settings.db ? "storage") [ "-${cfg.settings.db.storage}" ];
+        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
         Environment = [
-          "CMD_CONFIG_FILE=${cfg.workDir}/config.json"
+          "CMD_CONFIG_FILE=/run/${name}/config.json"
           "NODE_ENV=production"
         ];
-        Restart = "always";
-        User = name;
+
+        # Hardening
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
         PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          # Required for connecting to database sockets,
+          # and listening to unix socket at `cfg.settings.path`
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SocketBindAllow = lib.mkIf (cfg.settings.path == null) cfg.settings.port;
+        SocketBindDeny = "any";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged @obsolete"
+          "@pkey"
+        ];
+        UMask = "0007";
       };
     };
   };
diff --git a/nixos/modules/services/web-apps/hledger-web.nix b/nixos/modules/services/web-apps/hledger-web.nix
index 0fc283ff52191..be8ecc645e59c 100644
--- a/nixos/modules/services/web-apps/hledger-web.nix
+++ b/nixos/modules/services/web-apps/hledger-web.nix
@@ -7,7 +7,7 @@ in {
 
     enable = mkEnableOption (lib.mdDoc "hledger-web service");
 
-    serveApi = mkEnableOption (lib.mdDoc "Serve only the JSON web API, without the web UI");
+    serveApi = mkEnableOption (lib.mdDoc "serving only the JSON web API, without the web UI");
 
     host = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/web-apps/honk.nix b/nixos/modules/services/web-apps/honk.nix
index e8718774575b7..eb270a661ecb4 100644
--- a/nixos/modules/services/web-apps/honk.nix
+++ b/nixos/modules/services/web-apps/honk.nix
@@ -22,7 +22,7 @@ in
   options = {
     services.honk = {
       enable = lib.mkEnableOption (lib.mdDoc "the Honk server");
-      package = lib.mkPackageOptionMD pkgs "honk" { };
+      package = lib.mkPackageOption pkgs "honk" { };
 
       host = lib.mkOption {
         default = "127.0.0.1";
@@ -116,7 +116,7 @@ in
       unitConfig = {
         ConditionPathExists = [
           # Skip this service if the database already exists
-          "!$STATE_DIRECTORY/honk.db"
+          "!%S/honk/honk.db"
         ];
       };
     };
diff --git a/nixos/modules/services/web-apps/invidious.nix b/nixos/modules/services/web-apps/invidious.nix
index 5603ef7392e82..359aaabfe673a 100644
--- a/nixos/modules/services/web-apps/invidious.nix
+++ b/nixos/modules/services/web-apps/invidious.nix
@@ -10,82 +10,115 @@ let
   generatedHmacKeyFile = "/var/lib/invidious/hmac_key";
   generateHmac = cfg.hmacKeyFile == null;
 
-  serviceConfig = {
-    systemd.services.invidious = {
-      description = "Invidious (An alternative YouTube front-end)";
-      wants = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      preStart = lib.optionalString generateHmac ''
-        if [[ ! -e "${generatedHmacKeyFile}" ]]; then
-          ${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}"
-          chmod 0600 "${generatedHmacKeyFile}"
-        fi
-      '';
-
-      script = ''
-        configParts=()
-      ''
-      # autogenerated hmac_key
-      + lib.optionalString generateHmac ''
-        configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")")
-      ''
-      # generated settings file
-      + ''
-        configParts+=("$(< ${lib.escapeShellArg settingsFile})")
-      ''
-      # optional database password file
-      + lib.optionalString (cfg.database.host != null) ''
-        configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})")
-      ''
-      # optional extra settings file
-      + lib.optionalString (cfg.extraSettingsFile != null) ''
-        configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})")
-      ''
-      # explicitly specified hmac key file
-      + lib.optionalString (cfg.hmacKeyFile != null) ''
-        configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})")
-      ''
-      # merge all parts into a single configuration with later elements overriding previous elements
-      + ''
-        export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")"
-        exec ${cfg.package}/bin/invidious
-      '';
-
-      serviceConfig = {
-        RestartSec = "2s";
-        DynamicUser = true;
-        StateDirectory = "invidious";
-        StateDirectoryMode = "0750";
-
-        CapabilityBoundingSet = "";
-        PrivateDevices = true;
-        PrivateUsers = true;
-        ProtectHome = true;
-        ProtectKernelLogs = true;
-        ProtectProc = "invisible";
-        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
-        RestrictNamespaces = true;
-        SystemCallArchitectures = "native";
-        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
-
-        # Because of various issues Invidious must be restarted often, at least once a day, ideally
-        # every hour.
-        # This option enables the automatic restarting of the Invidious instance.
-        Restart = lib.mkDefault "always";
-        RuntimeMaxSec = lib.mkDefault "1h";
-      };
+  commonInvidousServiceConfig = {
+    description = "Invidious (An alternative YouTube front-end)";
+    wants = [ "network-online.target" ];
+    after = [ "network-online.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
+    requires = lib.optional cfg.database.createLocally "postgresql.service";
+    wantedBy = [ "multi-user.target" ];
+
+    serviceConfig = {
+      RestartSec = "2s";
+      DynamicUser = true;
+      User = lib.mkIf (cfg.database.createLocally || cfg.serviceScale > 1) "invidious";
+      StateDirectory = "invidious";
+      StateDirectoryMode = "0750";
+
+      CapabilityBoundingSet = "";
+      PrivateDevices = true;
+      PrivateUsers = true;
+      ProtectHome = true;
+      ProtectKernelLogs = true;
+      ProtectProc = "invisible";
+      RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+      RestrictNamespaces = true;
+      SystemCallArchitectures = "native";
+      SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
+
+      # Because of various issues Invidious must be restarted often, at least once a day, ideally
+      # every hour.
+      # This option enables the automatic restarting of the Invidious instance.
+      # To ensure multiple instances of Invidious are not restarted at the exact same time, a
+      # randomized extra offset of up to 5 minutes is added.
+      Restart = lib.mkDefault "always";
+      RuntimeMaxSec = lib.mkDefault "1h";
+      RuntimeRandomizedExtraSec = lib.mkDefault "5min";
     };
+  };
+  mkInvidiousService = scaleIndex:
+    lib.foldl' lib.recursiveUpdate commonInvidousServiceConfig [
+      # only generate the hmac file in the first service
+      (lib.optionalAttrs (scaleIndex == 0) {
+        preStart = lib.optionalString generateHmac ''
+          if [[ ! -e "${generatedHmacKeyFile}" ]]; then
+            ${pkgs.pwgen}/bin/pwgen 20 1 > "${generatedHmacKeyFile}"
+            chmod 0600 "${generatedHmacKeyFile}"
+          fi
+        '';
+      })
+      # configure the secondary services to run after the first service
+      (lib.optionalAttrs (scaleIndex > 0) {
+        after = commonInvidousServiceConfig.after ++ [ "invidious.service" ];
+        wants = commonInvidousServiceConfig.wants ++ [ "invidious.service" ];
+      })
+      {
+        script = ''
+          configParts=()
+        ''
+        # autogenerated hmac_key
+        + lib.optionalString generateHmac ''
+          configParts+=("$(${pkgs.jq}/bin/jq -R '{"hmac_key":.}' <"${generatedHmacKeyFile}")")
+        ''
+        # generated settings file
+        + ''
+          configParts+=("$(< ${lib.escapeShellArg settingsFile})")
+        ''
+        # optional database password file
+        + lib.optionalString (cfg.database.host != null) ''
+          configParts+=("$(${pkgs.jq}/bin/jq -R '{"db":{"password":.}}' ${lib.escapeShellArg cfg.database.passwordFile})")
+        ''
+        # optional extra settings file
+        + lib.optionalString (cfg.extraSettingsFile != null) ''
+          configParts+=("$(< ${lib.escapeShellArg cfg.extraSettingsFile})")
+        ''
+        # explicitly specified hmac key file
+        + lib.optionalString (cfg.hmacKeyFile != null) ''
+          configParts+=("$(< ${lib.escapeShellArg cfg.hmacKeyFile})")
+        ''
+        # configure threads for secondary instances
+        + lib.optionalString (scaleIndex > 0) ''
+          configParts+=('{"channel_threads":0, "feed_threads":0}')
+        ''
+        # configure different ports for the instances
+        + ''
+          configParts+=('{"port":${toString (cfg.port + scaleIndex)}}')
+        ''
+        # merge all parts into a single configuration with later elements overriding previous elements
+        + ''
+          export INVIDIOUS_CONFIG="$(${pkgs.jq}/bin/jq -s 'reduce .[] as $item ({}; . * $item)' <<<"''${configParts[*]}")"
+          exec ${cfg.package}/bin/invidious
+        '';
+      }
+    ];
 
-    services.invidious.settings = {
-      inherit (cfg) port;
+  serviceConfig = {
+    systemd.services = builtins.listToAttrs (builtins.genList
+      (scaleIndex: {
+        name = "invidious" + lib.optionalString (scaleIndex > 0) "-${builtins.toString scaleIndex}";
+        value = mkInvidiousService scaleIndex;
+      })
+      cfg.serviceScale);
 
+    services.invidious.settings = {
       # Automatically initialises and migrates the database if necessary
       check_tables = true;
 
       db = {
-        user = lib.mkDefault "kemal";
+        user = lib.mkDefault (
+          if (lib.versionAtLeast config.system.stateVersion "24.05")
+          then "invidious"
+          else "kemal"
+        );
         dbname = lib.mkDefault "invidious";
         port = cfg.database.port;
         # Blank for unix sockets, see
@@ -94,64 +127,74 @@ let
         # Not needed because peer authentication is enabled
         password = lib.mkIf (cfg.database.host == null) "";
       };
+
+      host_binding = cfg.address;
     } // (lib.optionalAttrs (cfg.domain != null) {
       inherit (cfg) domain;
     });
 
-    assertions = [{
-      assertion = cfg.database.host != null -> cfg.database.passwordFile != null;
-      message = "If database host isn't null, database password needs to be set";
-    }];
+    assertions = [
+      {
+        assertion = cfg.database.host != null -> cfg.database.passwordFile != null;
+        message = "If database host isn't null, database password needs to be set";
+      }
+      {
+        assertion = cfg.serviceScale >= 1;
+        message = "Service can't be scaled below one instance";
+      }
+    ];
   };
 
   # Settings necessary for running with an automatically managed local database
   localDatabaseConfig = lib.mkIf cfg.database.createLocally {
+    assertions = [
+      {
+        assertion = cfg.settings.db.user == cfg.settings.db.dbname;
+        message = ''
+          For local automatic database provisioning (services.invidious.database.createLocally == true)
+          to  work, the username used to connect to PostgreSQL must match the database name, that is
+          services.invidious.settings.db.user must match services.invidious.settings.db.dbname.
+          This is the default since NixOS 24.05. For older systems, it is normally safe to manually set
+          the user to "invidious" as the new user will be created with permissions
+          for the existing database. `REASSIGN OWNED BY kemal TO invidious;` may also be needed, it can be
+          run as `sudo -u postgres env psql --user=postgres --dbname=invidious -c 'reassign OWNED BY kemal to invidious;'`.
+        '';
+      }
+    ];
     # Default to using the local database if we create it
     services.invidious.database.host = lib.mkDefault null;
 
     services.postgresql = {
       enable = true;
+      ensureUsers = lib.singleton { name = cfg.settings.db.user; ensureDBOwnership = true; };
       ensureDatabases = lib.singleton cfg.settings.db.dbname;
-      ensureUsers = lib.singleton {
-        name = cfg.settings.db.user;
-        ensurePermissions = {
-          "DATABASE ${cfg.settings.db.dbname}" = "ALL PRIVILEGES";
-        };
-      };
-      # This is only needed because the unix user invidious isn't the same as
-      # the database user. This tells postgres to map one to the other.
-      identMap = ''
-        invidious invidious ${cfg.settings.db.user}
-      '';
-      # And this specifically enables peer authentication for only this
-      # database, which allows passwordless authentication over the postgres
-      # unix socket for the user map given above.
-      authentication = ''
-        local ${cfg.settings.db.dbname} ${cfg.settings.db.user} peer map=invidious
-      '';
     };
+  };
+
+  ytproxyConfig = lib.mkIf cfg.http3-ytproxy.enable {
+    systemd.services.http3-ytproxy = {
+      description = "HTTP3 ytproxy for Invidious";
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
 
-    systemd.services.invidious-db-clean = {
-      description = "Invidious database cleanup";
-      documentation = [ "https://docs.invidious.io/Database-Information-and-Maintenance.md" ];
-      startAt = lib.mkDefault "weekly";
-      path = [ config.services.postgresql.package ];
       script = ''
-        psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "DELETE FROM nonces * WHERE expire < current_timestamp"
-        psql ${cfg.settings.db.dbname} ${cfg.settings.db.user} -c "TRUNCATE TABLE videos"
+        mkdir -p socket
+        exec ${lib.getExe cfg.http3-ytproxy.package};
       '';
+
       serviceConfig = {
+        RestartSec = "2s";
         DynamicUser = true;
-        User = "invidious";
+        User = lib.mkIf cfg.nginx.enable config.services.nginx.user;
+        RuntimeDirectory = "http3-ytproxy";
+        WorkingDirectory = "/run/http3-ytproxy";
       };
     };
 
-    systemd.services.invidious = {
-      requires = [ "postgresql.service" ];
-      after = [ "postgresql.service" ];
-
-      serviceConfig = {
-        User = "invidious";
+    services.nginx.virtualHosts.${cfg.domain} = lib.mkIf cfg.nginx.enable {
+      locations."~ (^/videoplayback|^/vi/|^/ggpht/|^/sb/)" = {
+        proxyPass = "http://unix:/run/http3-ytproxy/socket/http-proxy.sock";
       };
     };
   };
@@ -162,14 +205,28 @@ let
       external_port = 80;
     };
 
-    services.nginx = {
+    services.nginx = let
+      ip = if cfg.address == "0.0.0.0" then "127.0.0.1" else cfg.address;
+    in
+    {
       enable = true;
       virtualHosts.${cfg.domain} = {
-        locations."/".proxyPass = "http://127.0.0.1:${toString cfg.port}";
+        locations."/".proxyPass =
+          if cfg.serviceScale == 1 then
+            "http://${ip}:${toString cfg.port}"
+          else "http://upstream-invidious";
 
         enableACME = lib.mkDefault true;
         forceSSL = lib.mkDefault true;
       };
+      upstreams = lib.mkIf (cfg.serviceScale > 1) {
+        "upstream-invidious".servers = builtins.listToAttrs (builtins.genList
+          (scaleIndex: {
+            name = "${ip}:${toString (cfg.port + scaleIndex)}";
+            value = { };
+          })
+          cfg.serviceScale);
+      };
     };
 
     assertions = [{
@@ -182,12 +239,7 @@ in
   options.services.invidious = {
     enable = lib.mkEnableOption (lib.mdDoc "Invidious");
 
-    package = lib.mkOption {
-      type = types.package;
-      default = pkgs.invidious;
-      defaultText = lib.literalExpression "pkgs.invidious";
-      description = lib.mdDoc "The Invidious package to use.";
-    };
+    package = lib.mkPackageOption pkgs "invidious" { };
 
     settings = lib.mkOption {
       type = settingsFormat.type;
@@ -222,6 +274,20 @@ in
       '';
     };
 
+    serviceScale = lib.mkOption {
+      type = types.int;
+      default = 1;
+      description = lib.mdDoc ''
+        How many invidious instances to run.
+
+        See https://docs.invidious.io/improve-public-instance/#2-multiple-invidious-processes for more details
+        on how this is intended to work. All instances beyond the first one have the options `channel_threads`
+        and `feed_threads` set to 0 to avoid conflicts with multiple instances refreshing subscriptions. Instances
+        will be configured to bind to consecutive ports starting with {option}`services.invidious.port` for the
+        first instance.
+      '';
+    };
+
     # This needs to be outside of settings to avoid infinite recursion
     # (determining if nginx should be enabled and therefore the settings
     # modified).
@@ -235,6 +301,16 @@ in
       '';
     };
 
+    address = lib.mkOption {
+      type = types.str;
+      # default from https://github.com/iv-org/invidious/blob/master/config/config.example.yml
+      default = if cfg.nginx.enable then "127.0.0.1" else "0.0.0.0";
+      defaultText = lib.literalExpression ''if config.services.invidious.nginx.enable then "127.0.0.1" else "0.0.0.0"'';
+      description = lib.mdDoc ''
+        The IP address Invidious should bind to.
+      '';
+    };
+
     port = lib.mkOption {
       type = types.port;
       # Default from https://docs.invidious.io/Configuration.md
@@ -300,11 +376,28 @@ in
         which can also be used to disable AMCE and TLS.
       '';
     };
+
+    http3-ytproxy = {
+      enable = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Whether to enable http3-ytproxy for faster loading of images and video playback.
+
+          If {option}`services.invidious.nginx.enable` is used, nginx will be configured automatically. If not, you
+          need to configure a reverse proxy yourself according to
+          https://docs.invidious.io/improve-public-instance/#3-speed-up-video-playback-with-http3-ytproxy.
+        '';
+      };
+
+      package = lib.mkPackageOptionMD pkgs "http3-ytproxy" { };
+    };
   };
 
   config = lib.mkIf cfg.enable (lib.mkMerge [
     serviceConfig
     localDatabaseConfig
     nginxConfig
+    ytproxyConfig
   ]);
 }
diff --git a/nixos/modules/services/web-apps/invoiceplane.nix b/nixos/modules/services/web-apps/invoiceplane.nix
index f419b75cf70fe..618bd848ebcb2 100644
--- a/nixos/modules/services/web-apps/invoiceplane.nix
+++ b/nixos/modules/services/web-apps/invoiceplane.nix
@@ -28,7 +28,19 @@ let
     REMOVE_INDEXPHP=true
   '';
 
-  extraConfig = hostName: cfg: pkgs.writeText "extraConfig.php" ''
+  mkPhpValue = v:
+    if isString v then escapeShellArg v
+    # NOTE: If any value contains a , (comma) this will not get escaped
+    else if isList v && any lib.strings.isCoercibleToString v then escapeShellArg (concatMapStringsSep "," toString v)
+    else if isInt v then toString v
+    else if isBool v then boolToString v
+    else abort "The Invoiceplane config value ${lib.generators.toPretty {} v} can not be encoded."
+  ;
+
+  extraConfig = hostName: cfg: let
+    settings = mapAttrsToList (k: v: "${k}=${mkPhpValue v}") cfg.settings;
+  in pkgs.writeText "extraConfig.php" ''
+    ${concatStringsSep "\n" settings}
     ${toString cfg.extraConfig}
   '';
 
@@ -182,11 +194,31 @@ let
             InvoicePlane configuration. Refer to
             <https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example>
             for details on supported values.
+
+            **Note**: Please pass structured settings via
+            `services.invoiceplane.sites.${name}.settings` instead, this option
+            will get deprecated in the future.
           '';
         };
 
-        cron = {
+        settings = mkOption {
+          type = types.attrsOf types.anything;
+          default = {};
+          description = lib.mdDoc ''
+            Structural InvoicePlane configuration. Refer to
+            <https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example>
+            for details and supported values.
+          '';
+          example = literalExpression ''
+            {
+              SETUP_COMPLETED = true;
+              DISABLE_SETUP = true;
+              IP_URL = "https://invoice.example.com";
+            }
+          '';
+        };
 
+        cron = {
           enable = mkOption {
             type = types.bool;
             default = false;
@@ -197,12 +229,10 @@ let
               on how to configure it.
             '';
           };
-
           key = mkOption {
             type = types.str;
             description = lib.mdDoc "Cron key taken from the administration page.";
           };
-
         };
 
       };
@@ -222,11 +252,11 @@ in
         };
 
         options.webserver = mkOption {
-          type = types.enum [ "caddy" ];
+          type = types.enum [ "caddy" "nginx" ];
           default = "caddy";
+          example = "nginx";
           description = lib.mdDoc ''
-            Which webserver to use for virtual host management. Currently only
-            caddy is supported.
+            Which webserver to use for virtual host management.
           '';
         };
       };
@@ -239,8 +269,14 @@ in
   # implementation
   config = mkIf (eachSite != {}) (mkMerge [{
 
-    assertions = flatten (mapAttrsToList (hostName: cfg:
-      [{ assertion = cfg.database.createLocally -> cfg.database.user == user;
+    warnings = flatten (mapAttrsToList (hostName: cfg: [
+      (optional (cfg.extraConfig != null) ''
+        services.invoiceplane.sites."${hostName}".extraConfig will be deprecated in future releases, please use the settings option now.
+      '')
+    ]) eachSite);
+
+    assertions = flatten (mapAttrsToList (hostName: cfg: [
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
         message = ''services.invoiceplane.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned'';
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -354,5 +390,39 @@ in
     };
   })
 
+  (mkIf (cfg.webserver == "nginx") {
+    services.nginx = {
+      enable = true;
+      virtualHosts = mapAttrs' (hostName: cfg: (
+        nameValuePair hostName {
+          root = pkg hostName cfg;
+          extraConfig = ''
+            index index.php index.html index.htm;
+
+            if (!-e $request_filename){
+              rewrite ^(.*)$ /index.php break;
+            }
+          '';
+
+          locations = {
+            "/setup".extraConfig = ''
+              rewrite ^(.*)$ http://${hostName}/ redirect;
+            '';
+
+            "~ .php$" = {
+              extraConfig = ''
+                fastcgi_split_path_info ^(.+\.php)(/.+)$;
+                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+                fastcgi_pass unix:${config.services.phpfpm.pools."invoiceplane-${hostName}".socket};
+                include ${config.services.nginx.package}/conf/fastcgi_params;
+                include ${config.services.nginx.package}/conf/fastcgi.conf;
+              '';
+            };
+          };
+        }
+      )) eachSite;
+    };
+  })
+
   ]);
 }
diff --git a/nixos/modules/services/web-apps/isso.nix b/nixos/modules/services/web-apps/isso.nix
index 1a852ec352f2c..6cb2d9ec785eb 100644
--- a/nixos/modules/services/web-apps/isso.nix
+++ b/nixos/modules/services/web-apps/isso.nix
@@ -12,11 +12,11 @@ in {
   options = {
     services.isso = {
       enable = mkEnableOption (lib.mdDoc ''
-        A commenting server similar to Disqus.
+        isso, 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.
+        below 20 requests per second
       '');
 
       settings = mkOption {
diff --git a/nixos/modules/services/web-apps/jirafeau.nix b/nixos/modules/services/web-apps/jirafeau.nix
index b2e2741671641..5f754d824a281 100644
--- a/nixos/modules/services/web-apps/jirafeau.nix
+++ b/nixos/modules/services/web-apps/jirafeau.nix
@@ -92,12 +92,7 @@ in
       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 = lib.mdDoc "Jirafeau package to use";
-    };
+    package = mkPackageOption pkgs "jirafeau" { };
 
     poolConfig = mkOption {
       type = with types; attrsOf (oneOf [ str int bool ]);
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 3825b03c24496..c4505534d635e 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -35,6 +35,7 @@ let
       domain = cfg.hostName;
       muc = "conference.${cfg.hostName}";
       focus = "focus.${cfg.hostName}";
+      jigasi = "jigasi.${cfg.hostName}";
     };
     bosh = "//${cfg.hostName}/http-bind";
     websocket = "wss://${cfg.hostName}/xmpp-websocket";
@@ -105,9 +106,9 @@ in
         type = bool;
         default = true;
         description = lib.mdDoc ''
-          Whether to enable Jitsi Videobridge instance and configure it to connect to Prosody.
+          Jitsi Videobridge instance and configure it to connect to Prosody.
 
-          Additional configuration is possible with {option}`services.jitsi-videobridge`.
+          Additional configuration is possible with {option}`services.jitsi-videobridge`
         '';
       };
 
@@ -145,6 +146,16 @@ in
       '';
     };
 
+    jigasi.enable = mkOption {
+      type = bool;
+      default = false;
+      description = ''
+        Whether to enable jigasi instance and configure it to connect to Prosody.
+
+        Additional configuration is possible with <option>services.jigasi</option>.
+      '';
+    };
+
     nginx.enable = mkOption {
       type = bool;
       default = true;
@@ -169,6 +180,15 @@ in
         off if you want to configure it manually.
       '';
     };
+
+    excalidraw.enable = mkEnableOption (lib.mdDoc "Excalidraw collaboration backend for Jitsi");
+    excalidraw.port = mkOption {
+      type = types.port;
+      default = 3002;
+      description = lib.mdDoc ''The port which the Excalidraw backend for Jitsi should listen to.'';
+    };
+
+    secureDomain.enable = mkEnableOption (lib.mdDoc "Authenticated room creation");
   };
 
   config = mkIf cfg.enable {
@@ -192,41 +212,121 @@ in
           roomLocking = false;
           roomDefaultPublicJids = true;
           extraConfig = ''
+            restrict_room_creation = true
             storage = "memory"
+            admins = { "focus@auth.${cfg.hostName}" }
           '';
         }
         {
-          domain = "internal.${cfg.hostName}";
+          domain = "breakout.${cfg.hostName}";
+          name = "Jitsi Meet Breakout MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
+          extraConfig = ''
+            restrict_room_creation = true
+            storage = "memory"
+            admins = { "focus@auth.${cfg.hostName}" }
+          '';
+        }
+        {
+          domain = "internal.auth.${cfg.hostName}";
           name = "Jitsi Meet Videobridge MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
           extraConfig = ''
             storage = "memory"
-            admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}" }
+            admins = { "focus@auth.${cfg.hostName}", "jvb@auth.${cfg.hostName}", "jigasi@auth.${cfg.hostName}" }
           '';
           #-- muc_room_cache_size = 1000
         }
+        {
+          domain = "lobby.${cfg.hostName}";
+          name = "Jitsi Meet Lobby MUC";
+          roomLocking = false;
+          roomDefaultPublicJids = true;
+          extraConfig = ''
+            restrict_room_creation = true
+            storage = "memory"
+          '';
+        }
+      ];
+      extraModules = [
+        "pubsub"
+        "smacks"
+        "speakerstats"
+        "external_services"
+        "conference_duration"
+        "end_conference"
+        "muc_lobby_rooms"
+        "muc_breakout_rooms"
+        "av_moderation"
+        "muc_hide_all"
+        "muc_meeting_id"
+        "muc_domain_mapper"
+        "muc_rate_limit"
+        "limits_exception"
+        "persistent_lobby"
+        "room_metadata"
       ];
-      extraModules = [ "pubsub" "smacks" ];
       extraPluginPaths = [ "${pkgs.jitsi-meet-prosody}/share/prosody-plugins" ];
-      extraConfig = lib.mkMerge [ (mkAfter ''
-        Component "focus.${cfg.hostName}" "client_proxy"
-          target_address = "focus@auth.${cfg.hostName}"
+      extraConfig = lib.mkMerge [
+        (mkAfter ''
+          Component "focus.${cfg.hostName}" "client_proxy"
+            target_address = "focus@auth.${cfg.hostName}"
+
+          Component "jigasi.${cfg.hostName}" "client_proxy"
+            target_address = "jigasi@auth.${cfg.hostName}"
+
+          Component "speakerstats.${cfg.hostName}" "speakerstats_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "conferenceduration.${cfg.hostName}" "conference_duration_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "endconference.${cfg.hostName}" "end_conference"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "avmoderation.${cfg.hostName}" "av_moderation_component"
+            muc_component = "conference.${cfg.hostName}"
+
+          Component "metadata.${cfg.hostName}" "room_metadata_component"
+            muc_component = "conference.${cfg.hostName}"
+            breakout_rooms_component = "breakout.${cfg.hostName}"
         '')
         (mkBefore ''
+          muc_mapper_domain_base = "${cfg.hostName}"
+
           cross_domain_websocket = true;
           consider_websocket_secure = true;
+
+          unlimited_jids = {
+            "focus@auth.${cfg.hostName}",
+            "jvb@auth.${cfg.hostName}"
+          }
         '')
       ];
       virtualHosts.${cfg.hostName} = {
         enabled = true;
         domain = cfg.hostName;
         extraConfig = ''
-          authentication = "anonymous"
+          authentication = ${if cfg.secureDomain.enable then "\"internal_hashed\"" else "\"jitsi-anonymous\""}
           c2s_require_encryption = false
           admins = { "focus@auth.${cfg.hostName}" }
           smacks_max_unacked_stanzas = 5
           smacks_hibernation_time = 60
           smacks_max_hibernated_sessions = 1
           smacks_max_old_sessions = 1
+
+          av_moderation_component = "avmoderation.${cfg.hostName}"
+          speakerstats_component = "speakerstats.${cfg.hostName}"
+          conference_duration_component = "conferenceduration.${cfg.hostName}"
+          end_conference_component = "endconference.${cfg.hostName}"
+
+          c2s_require_encryption = false
+          lobby_muc = "lobby.${cfg.hostName}"
+          breakout_rooms_muc = "breakout.${cfg.hostName}"
+          room_metadata_component = "metadata.${cfg.hostName}"
+          main_muc = "conference.${cfg.hostName}"
         '';
         ssl = {
           cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
@@ -237,7 +337,7 @@ in
         enabled = true;
         domain = "auth.${cfg.hostName}";
         extraConfig = ''
-          authentication = "internal_plain"
+          authentication = "internal_hashed"
         '';
         ssl = {
           cert = "/var/lib/jitsi-meet/jitsi-meet.crt";
@@ -252,6 +352,14 @@ in
           c2s_require_encryption = false
         '';
       };
+      virtualHosts."guest.${cfg.hostName}" = {
+        enabled = true;
+        domain = "guest.${cfg.hostName}";
+        extraConfig = ''
+          authentication = "anonymous"
+          c2s_require_encryption = false
+        '';
+      };
     };
     systemd.services.prosody = mkIf cfg.prosody.enable {
       preStart = let
@@ -262,7 +370,10 @@ in
         ${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)"
+      '' + optionalString cfg.jigasi.enable ''
+        ${config.services.prosody.package}/bin/prosodyctl register jigasi auth.${cfg.hostName} "$(cat /var/lib/jitsi-meet/jigasi-user-secret)"
       '';
+
       serviceConfig = {
         EnvironmentFile = [ "/var/lib/jitsi-meet/secrets-env" ];
         SupplementaryGroups = [ "jitsi-meet" ];
@@ -270,20 +381,20 @@ in
       reloadIfChanged = true;
     };
 
-    users.groups.jitsi-meet = {};
+    users.groups.jitsi-meet = { };
     systemd.tmpfiles.rules = [
       "d '/var/lib/jitsi-meet' 0750 root jitsi-meet - -"
     ];
 
     systemd.services.jitsi-meet-init-secrets = {
       wantedBy = [ "multi-user.target" ];
-      before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service");
+      before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service") ++ (optional cfg.jigasi.enable "jigasi.service");
       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");
+        secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optionals cfg.jigasi.enable [ "jigasi-user-secret" "jigasi-component-secret" ]) ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
       in
       ''
         cd /var/lib/jitsi-meet
@@ -297,6 +408,7 @@ in
 
         # for easy access in prosody
         echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env
+        echo "JIGASI_COMPONENT_SECRET=$(cat jigasi-component-secret)" >> secrets-env
         chown root:jitsi-meet secrets-env
         chmod 640 secrets-env
       ''
@@ -317,6 +429,20 @@ in
       '';
     };
 
+    systemd.services.jitsi-excalidraw = mkIf cfg.excalidraw.enable {
+      description = "Excalidraw collaboration backend for Jitsi";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.PORT = toString cfg.excalidraw.port;
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.jitsi-excalidraw}/bin/jitsi-excalidraw-backend";
+        Restart = "on-failure";
+        Group = "jitsi-meet";
+      };
+    };
+
     services.nginx = mkIf cfg.nginx.enable {
       enable = mkDefault true;
       virtualHosts.${cfg.hostName} = {
@@ -345,12 +471,23 @@ in
         locations."=/external_api.js" = mkDefault {
           alias = "${pkgs.jitsi-meet}/libs/external_api.min.js";
         };
+        locations."=/_api/room-info" = {
+          proxyPass = "http://localhost:5280/room-info";
+          extraConfig = ''
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header Host $host;
+          '';
+        };
         locations."=/config.js" = mkDefault {
           alias = overrideJs "${pkgs.jitsi-meet}/config.js" "config" (recursiveUpdate defaultCfg cfg.config) cfg.extraConfig;
         };
         locations."=/interface_config.js" = mkDefault {
           alias = overrideJs "${pkgs.jitsi-meet}/interface_config.js" "interfaceConfig" cfg.interfaceConfig "";
         };
+        locations."/socket.io/" = mkIf cfg.excalidraw.enable {
+          proxyPass = "http://127.0.0.1:${toString cfg.excalidraw.port}";
+          proxyWebsockets = true;
+        };
       };
     };
 
@@ -359,8 +496,8 @@ in
       virtualHosts.${cfg.hostName} = {
         extraConfig =
         let
-          templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" {} ''
-            cp -R ${pkgs.jitsi-meet}/* .
+          templatedJitsiMeet = pkgs.runCommand "templated-jitsi-meet" { } ''
+            cp -R --no-preserve=all ${pkgs.jitsi-meet}/* .
             for file in *.html **/*.html ; do
               ${pkgs.sd}/bin/sd '<!--#include virtual="(.*)" -->' '{{ include "$1" }}' $file
             done
@@ -390,13 +527,24 @@ in
       };
     };
 
+    services.jitsi-meet.config = recursiveUpdate
+      (mkIf cfg.excalidraw.enable {
+        whiteboard = {
+          enabled = true;
+          collabServerBaseUrl = "https://${cfg.hostName}";
+        };
+      })
+      (mkIf cfg.secureDomain.enable {
+        hosts.anonymousdomain = "guest.${cfg.hostName}";
+      });
+
     services.jitsi-videobridge = mkIf cfg.videobridge.enable {
       enable = true;
       xmppConfigs."localhost" = {
         userName = "jvb";
         domain = "auth.${cfg.hostName}";
         passwordFile = "/var/lib/jitsi-meet/videobridge-secret";
-        mucJids = "jvbbrewery@internal.${cfg.hostName}";
+        mucJids = "jvbbrewery@internal.auth.${cfg.hostName}";
         disableCertificateVerification = true;
       };
     };
@@ -409,17 +557,27 @@ in
       userName = "focus";
       userPasswordFile = "/var/lib/jitsi-meet/jicofo-user-secret";
       componentPasswordFile = "/var/lib/jitsi-meet/jicofo-component-secret";
-      bridgeMuc = "jvbbrewery@internal.${cfg.hostName}";
+      bridgeMuc = "jvbbrewery@internal.auth.${cfg.hostName}";
       config = mkMerge [{
         jicofo.xmpp.service.disable-certificate-verification = true;
         jicofo.xmpp.client.disable-certificate-verification = true;
-      #} (lib.mkIf cfg.jibri.enable {
-       } (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
-         jicofo.jibri = {
-           brewery-jid = "JibriBrewery@internal.${cfg.hostName}";
-           pending-timeout = "90";
-         };
-      })];
+      }
+        (lib.mkIf (config.services.jibri.enable || cfg.jibri.enable) {
+          jicofo.jibri = {
+            brewery-jid = "JibriBrewery@internal.auth.${cfg.hostName}";
+            pending-timeout = "90";
+          };
+        })
+        (lib.mkIf cfg.secureDomain.enable {
+          jicofo = {
+            authentication = {
+              enabled = "true";
+              type = "XMPP";
+              login-url = cfg.hostName;
+            };
+            xmpp.client.client-proxy = "focus.${cfg.hostName}";
+          };
+        })];
     };
 
     services.jibri = mkIf cfg.jibri.enable {
@@ -430,7 +588,7 @@ in
         xmppDomain = cfg.hostName;
 
         control.muc = {
-          domain = "internal.${cfg.hostName}";
+          domain = "internal.auth.${cfg.hostName}";
           roomName = "JibriBrewery";
           nickname = "jibri";
         };
@@ -452,6 +610,20 @@ in
         stripFromRoomDomain = "conference.";
       };
     };
+
+    services.jigasi = mkIf cfg.jigasi.enable {
+      enable = true;
+      xmppHost = "localhost";
+      xmppDomain = cfg.hostName;
+      userDomain = "auth.${cfg.hostName}";
+      userName = "jigasi";
+      userPasswordFile = "/var/lib/jitsi-meet/jigasi-user-secret";
+      componentPasswordFile = "/var/lib/jitsi-meet/jigasi-component-secret";
+      bridgeMuc = "jigasibrewery@internal.${cfg.hostName}";
+      config = {
+        "org.jitsi.jigasi.ALWAYS_TRUST_MODE_ENABLED" = "true";
+      };
+    };
   };
 
   meta.doc = ./jitsi-meet.md;
diff --git a/nixos/modules/services/web-apps/kavita.nix b/nixos/modules/services/web-apps/kavita.nix
index ca9cd01d403d3..c3e39f0b54761 100644
--- a/nixos/modules/services/web-apps/kavita.nix
+++ b/nixos/modules/services/web-apps/kavita.nix
@@ -12,7 +12,7 @@ in {
       description = lib.mdDoc "User account under which Kavita runs.";
     };
 
-    package = lib.mkPackageOptionMD pkgs "kavita" { };
+    package = lib.mkPackageOption pkgs "kavita" { };
 
     dataDir = lib.mkOption {
       default = "/var/lib/kavita";
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
index a7e4fab8ea287..6d2948913b194 100644
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixos/modules/services/web-apps/keycloak.nix
@@ -11,6 +11,7 @@ let
     mkChangedOptionModule
     mkRenamedOptionModule
     mkRemovedOptionModule
+    mkPackageOption
     concatStringsSep
     mapAttrsToList
     escapeShellArg
@@ -24,7 +25,6 @@ let
     maintainers
     catAttrs
     collect
-    splitString
     hasPrefix
     ;
 
@@ -246,14 +246,7 @@ in
         };
       };
 
-      package = mkOption {
-        type = package;
-        default = pkgs.keycloak;
-        defaultText = literalExpression "pkgs.keycloak";
-        description = lib.mdDoc ''
-          Keycloak package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "keycloak" { };
 
       initialAdminPassword = mkOption {
         type = str;
@@ -335,7 +328,8 @@ in
             };
 
             hostname = mkOption {
-              type = str;
+              type = nullOr str;
+              default = null;
               example = "keycloak.example.com";
               description = lib.mdDoc ''
                 The hostname part of the public URL used as base for
@@ -457,7 +451,7 @@ in
 
       keycloakConfig = lib.generators.toKeyValue {
         mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
-          mkValueString = v: with builtins;
+          mkValueString = v:
             if isInt v then toString v
             else if isString v then v
             else if true == v then "true"
@@ -486,6 +480,14 @@ in
             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";
           }
+          {
+            assertion = cfg.settings.hostname != null || cfg.settings.hostname-url or null != null;
+            message = "Setting the Keycloak hostname is required, see `services.keycloak.settings.hostname`";
+          }
+          {
+            assertion = !(cfg.settings.hostname != null && cfg.settings.hostname-url or null != null);
+            message = "`services.keycloak.settings.hostname` and `services.keycloak.settings.hostname-url` are mutually exclusive";
+          }
         ];
 
         environment.systemPackages = [ keycloakBuild ];
diff --git a/nixos/modules/services/web-apps/lanraragi.nix b/nixos/modules/services/web-apps/lanraragi.nix
new file mode 100644
index 0000000000000..6703da005ab09
--- /dev/null
+++ b/nixos/modules/services/web-apps/lanraragi.nix
@@ -0,0 +1,93 @@
+{ pkgs, lib, config, ... }:
+
+let
+  cfg = config.services.lanraragi;
+in
+{
+  meta.maintainers = with lib.maintainers; [ tomasajt ];
+
+  options.services = {
+    lanraragi = {
+      enable = lib.mkEnableOption (lib.mdDoc "LANraragi");
+      package = lib.mkPackageOption pkgs "lanraragi" { };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 3000;
+        description = lib.mdDoc "Port for LANraragi's web interface.";
+      };
+
+      passwordFile = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        example = "/run/keys/lanraragi-password";
+        description = lib.mdDoc ''
+          A file containing the password for LANraragi's admin interface.
+        '';
+      };
+
+      redis = {
+        port = lib.mkOption {
+          type = lib.types.port;
+          default = 6379;
+          description = lib.mdDoc "Port for LANraragi's Redis server.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/run/keys/redis-lanraragi-password";
+          description = lib.mdDoc ''
+            A file containing the password for LANraragi's Redis server.
+          '';
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.redis.servers.lanraragi = {
+      enable = true;
+      port = cfg.redis.port;
+      requirePassFile = cfg.redis.passwordFile;
+    };
+
+    systemd.services.lanraragi = {
+      description = "LANraragi main service";
+      after = [ "network.target" "redis-lanraragi.service" ];
+      requires = [ "redis-lanraragi.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = lib.getExe cfg.package;
+        DynamicUser = true;
+        StateDirectory = "lanraragi";
+        RuntimeDirectory = "lanraragi";
+        LogsDirectory = "lanraragi";
+        Restart = "on-failure";
+        WorkingDirectory = "/var/lib/lanraragi";
+      };
+      environment = {
+        "LRR_TEMP_DIRECTORY" = "/run/lanraragi";
+        "LRR_LOG_DIRECTORY" = "/var/log/lanraragi";
+        "LRR_NETWORK" = "http://*:${toString cfg.port}";
+        "HOME" = "/var/lib/lanraragi";
+      };
+      preStart = ''
+        cat > lrr.conf <<EOF
+        {
+          redis_address => "127.0.0.1:${toString cfg.redis.port}",
+          redis_password => "${lib.optionalString (cfg.redis.passwordFile != null) ''$(head -n1 ${cfg.redis.passwordFile})''}",
+          redis_database => "0",
+          redis_database_minion => "1",
+          redis_database_config => "2",
+          redis_database_search => "3",
+        }
+        EOF
+      '' + lib.optionalString (cfg.passwordFile != null) ''
+        ${lib.getExe pkgs.redis} -h 127.0.0.1 -p ${toString cfg.redis.port} ${lib.optionalString (cfg.redis.passwordFile != null) ''-a "$(head -n1 ${cfg.redis.passwordFile})"''}<<EOF
+          SELECT 2
+          HSET LRR_CONFIG password $(${cfg.package}/bin/helpers/lrr-make-password-hash $(head -n1 ${cfg.passwordFile}))
+        EOF
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
index 20d9dcb7c2661..bde9051a70330 100644
--- a/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixos/modules/services/web-apps/lemmy.nix
@@ -17,11 +17,11 @@ in
     enable = mkEnableOption (lib.mdDoc "lemmy a federated alternative to reddit in rust");
 
     server = {
-      package = mkPackageOptionMD pkgs "lemmy-server" {};
+      package = mkPackageOption pkgs "lemmy-server" {};
     };
 
     ui = {
-      package = mkPackageOptionMD pkgs "lemmy-ui" {};
+      package = mkPackageOption pkgs "lemmy-ui" {};
 
       port = mkOption {
         type = types.port;
@@ -146,7 +146,7 @@ in
         ensureDatabases = [ cfg.settings.database.database ];
         ensureUsers = [{
           name = cfg.settings.database.user;
-          ensurePermissions."DATABASE ${cfg.settings.database.database}" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }];
       };
 
diff --git a/nixos/modules/services/web-apps/mainsail.nix b/nixos/modules/services/web-apps/mainsail.nix
index f335d9b015d49..95de2c5640b4b 100644
--- a/nixos/modules/services/web-apps/mainsail.nix
+++ b/nixos/modules/services/web-apps/mainsail.nix
@@ -8,12 +8,7 @@ in
   options.services.mainsail = {
     enable = mkEnableOption (lib.mdDoc "a modern and responsive user interface for Klipper");
 
-    package = mkOption {
-      type = types.package;
-      description = lib.mdDoc "Mainsail package to be used in the module";
-      default = pkgs.mainsail;
-      defaultText = literalExpression "pkgs.mainsail";
-    };
+    package = mkPackageOption pkgs "mainsail" { };
 
     hostName = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/web-apps/mastodon.nix b/nixos/modules/services/web-apps/mastodon.nix
index 2aab97438b7d6..538e728fcc72f 100644
--- a/nixos/modules/services/web-apps/mastodon.nix
+++ b/nixos/modules/services/web-apps/mastodon.nix
@@ -17,9 +17,6 @@ let
     WEB_CONCURRENCY = toString cfg.webProcesses;
     MAX_THREADS = toString cfg.webThreads;
 
-    # mastodon-streaming concurrency.
-    STREAMING_CLUSTER_NUM = toString cfg.streamingProcesses;
-
     DB_USER = cfg.database.user;
 
     REDIS_HOST = cfg.redis.host;
@@ -33,13 +30,15 @@ let
     PAPERCLIP_ROOT_PATH = "/var/lib/mastodon/public-system";
     PAPERCLIP_ROOT_URL = "/system";
     ES_ENABLED = if (cfg.elasticsearch.host != null) then "true" else "false";
-    ES_HOST = cfg.elasticsearch.host;
-    ES_PORT = toString(cfg.elasticsearch.port);
 
     TRUSTED_PROXY_IP = cfg.trustedProxy;
   }
   // 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; }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_HOST = cfg.elasticsearch.host; }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PORT = toString(cfg.elasticsearch.port); }
+  // lib.optionalAttrs (cfg.elasticsearch.host != null) { ES_PRESET = cfg.elasticsearch.preset; }
+  // lib.optionalAttrs (cfg.elasticsearch.user != null) { ES_USER = cfg.elasticsearch.user; }
   // cfg.extraConfig;
 
   systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@mount" "@obsolete" "@privileged" "@setuid" ];
@@ -137,12 +136,48 @@ let
         # System Call Filtering
         SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
       } // cfgService;
-      path = with pkgs; [ file imagemagick ffmpeg ];
+      path = with pkgs; [ ffmpeg-headless file imagemagick ];
     })
   ) cfg.sidekiqProcesses;
 
+  streamingUnits = builtins.listToAttrs
+      (map (i: {
+        name = "mastodon-streaming-${toString i}";
+        value = {
+          after = [ "network.target" "mastodon-init-dirs.service" ]
+            ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+            ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+          requires = [ "mastodon-init-dirs.service" ]
+            ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
+            ++ lib.optional cfg.automaticMigrations "mastodon-init-db.service";
+          wantedBy = [ "mastodon.target" "mastodon-streaming.target" ];
+          description = "Mastodon streaming ${toString i}";
+          environment = env // { SOCKET = "/run/mastodon-streaming/streaming-${toString i}.socket"; };
+          serviceConfig = {
+            ExecStart = "${cfg.package}/run-streaming.sh";
+            Restart = "always";
+            RestartSec = 20;
+            EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
+            WorkingDirectory = cfg.package;
+            # Runtime directory and mode
+            RuntimeDirectory = "mastodon-streaming";
+            RuntimeDirectoryMode = "0750";
+            # System Call Filtering
+            SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
+          } // cfgService;
+        };
+      })
+      (lib.range 1 cfg.streamingProcesses));
+
 in {
 
+  imports = [
+    (lib.mkRemovedOptionModule
+      [ "services" "mastodon" "streamingPort" ]
+      "Mastodon currently doesn't support streaming via TCP ports. Please open a PR if you need this."
+    )
+  ];
+
   options = {
     services.mastodon = {
       enable = lib.mkEnableOption (lib.mdDoc "Mastodon, a federated social network server");
@@ -191,18 +226,13 @@ in {
         default = "mastodon";
       };
 
-      streamingPort = lib.mkOption {
-        description = lib.mdDoc "TCP port used by the mastodon-streaming service.";
-        type = lib.types.port;
-        default = 55000;
-      };
       streamingProcesses = lib.mkOption {
         description = lib.mdDoc ''
-          Processes used by the mastodon-streaming service.
-          Defaults to the number of CPU cores minus one.
+          Number of processes used by the mastodon-streaming service.
+          Please define this explicitly, recommended is the amount of your CPU cores minus one.
         '';
-        type = lib.types.nullOr lib.types.int;
-        default = null;
+        type = lib.types.ints.positive;
+        example = 3;
       };
 
       webPort = lib.mkOption {
@@ -485,6 +515,31 @@ in {
           type = lib.types.port;
           default = 9200;
         };
+
+        preset = lib.mkOption {
+          description = lib.mdDoc ''
+            It controls the ElasticSearch indices configuration (number of shards and replica).
+          '';
+          type = lib.types.enum [ "single_node_cluster" "small_cluster" "large_cluster" ];
+          default = "single_node_cluster";
+          example = "large_cluster";
+        };
+
+        user = lib.mkOption {
+          description = lib.mdDoc "Used for optionally authenticating with Elasticsearch.";
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+          example = "elasticsearch-mastodon";
+        };
+
+        passwordFile = lib.mkOption {
+          description = lib.mdDoc ''
+            Path to file containing password for optionally authenticating with Elasticsearch.
+          '';
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/var/lib/mastodon/secrets/elasticsearch-password";
+        };
       };
 
       package = lib.mkOption {
@@ -557,7 +612,7 @@ in {
   config = lib.mkIf cfg.enable (lib.mkMerge [{
     assertions = [
       {
-        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user);
+        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.database.user && cfg.database.user == cfg.database.name);
         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
@@ -603,6 +658,12 @@ in {
       after = [ "network.target" ];
     };
 
+    systemd.targets.mastodon-streaming = {
+      description = "Target for all Mastodon streaming services";
+      wantedBy = [ "multi-user.target" "mastodon.target" ];
+      after = [ "network.target" ];
+    };
+
     systemd.services.mastodon-init-dirs = {
       script = ''
         umask 077
@@ -631,6 +692,8 @@ in {
         DB_PASS="$(cat ${cfg.database.passwordFile})"
       '' + lib.optionalString cfg.smtp.authenticate ''
         SMTP_PASSWORD="$(cat ${cfg.smtp.passwordFile})"
+      '' + lib.optionalString (cfg.elasticsearch.passwordFile != null) ''
+        ES_PASS="$(cat ${cfg.elasticsearch.passwordFile})"
       '' + ''
         EOF
       '';
@@ -648,31 +711,28 @@ in {
     systemd.services.mastodon-init-db = lib.mkIf cfg.automaticMigrations {
       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
-
+        export PGPASSWORD="$(cat '${cfg.database.passwordFile}')"
       '' + ''
-        if [ `psql ${cfg.database.name} -c \
+        if [ `psql -c \
                 "select count(*) from pg_class c \
                 join pg_namespace s on s.oid = c.relnamespace \
                 where s.nspname not in ('pg_catalog', 'pg_toast', 'information_schema') \
                 and s.nspname not like 'pg_temp%';" | sed -n 3p` -eq 0 ]; then
+          echo "Seeding database"
           SAFETY_ASSURED=1 rails db:schema:load
           rails db:seed
         else
+          echo "Migrating database (this might be a noop)"
           rails db:migrate
         fi
       '' +  lib.optionalString (!databaseActuallyCreateLocally) ''
-        rm $PGPASSFILE
-        unset PGPASSFILE
+        unset PGPASSWORD
       '';
       path = [ cfg.package pkgs.postgresql ];
       environment = env // lib.optionalAttrs (!databaseActuallyCreateLocally) {
         PGHOST = cfg.database.host;
+        PGPORT = toString cfg.database.port;
+        PGDATABASE = cfg.database.name;
         PGUSER = cfg.database.user;
       };
       serviceConfig = {
@@ -688,33 +748,6 @@ in {
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service";
     };
 
-    systemd.services.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 = [ "mastodon.target" ];
-      description = "Mastodon streaming";
-      environment = env // (if cfg.enableUnixSocket
-        then { SOCKET = "/run/mastodon-streaming/streaming.socket"; }
-        else { PORT = toString(cfg.streamingPort); }
-      );
-      serviceConfig = {
-        ExecStart = "${cfg.package}/run-streaming.sh";
-        Restart = "always";
-        RestartSec = 20;
-        EnvironmentFile = [ "/var/lib/mastodon/.secrets_env" ] ++ cfg.extraEnvFiles;
-        WorkingDirectory = cfg.package;
-        # Runtime directory and mode
-        RuntimeDirectory = "mastodon-streaming";
-        RuntimeDirectoryMode = "0750";
-        # System Call Filtering
-        SystemCallFilter = [ ("~" + lib.concatStringsSep " " (systemCallsList ++ [ "@memlock" "@resources" ])) "pipe" "pipe2" ];
-      } // cfgService;
-    };
-
     systemd.services.mastodon-web = {
       after = [ "network.target" "mastodon-init-dirs.service" ]
         ++ lib.optional databaseActuallyCreateLocally "postgresql.service"
@@ -740,7 +773,7 @@ in {
         # System Call Filtering
         SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "@chown" "pipe" "pipe2" ];
       } // cfgService;
-      path = with pkgs; [ file imagemagick ffmpeg ];
+      path = with pkgs; [ ffmpeg-headless file imagemagick ];
     };
 
     systemd.services.mastodon-media-auto-remove = lib.mkIf cfg.mediaAutoRemove.enable {
@@ -780,10 +813,20 @@ in {
         };
 
         locations."/api/v1/streaming/" = {
-          proxyPass = (if cfg.enableUnixSocket then "http://unix:/run/mastodon-streaming/streaming.socket" else "http://127.0.0.1:${toString(cfg.streamingPort)}/");
+          proxyPass = "http://mastodon-streaming";
           proxyWebsockets = true;
         };
       };
+      upstreams.mastodon-streaming = {
+        extraConfig = ''
+          least_conn;
+        '';
+        servers = builtins.listToAttrs
+          (map (i: {
+            name = "unix:/run/mastodon-streaming/streaming-${toString i}.socket";
+            value = { };
+          }) (lib.range 1 cfg.streamingProcesses));
+      };
     };
 
     services.postfix = lib.mkIf (cfg.smtp.createLocally && cfg.smtp.host == "127.0.0.1") {
@@ -799,8 +842,8 @@ in {
       enable = true;
       ensureUsers = [
         {
-          name = cfg.database.user;
-          ensurePermissions."DATABASE ${cfg.database.name}" = "ALL PRIVILEGES";
+          name = cfg.database.name;
+          ensureDBOwnership = true;
         }
       ];
       ensureDatabases = [ cfg.database.name ];
@@ -819,7 +862,7 @@ in {
 
     users.groups.${cfg.group}.members = lib.optional cfg.configureNginx config.services.nginx.user;
   }
-  { systemd.services = sidekiqUnits; }
+  { systemd.services = lib.mkMerge [ sidekiqUnits streamingUnits ]; }
   ]);
 
   meta.maintainers = with lib.maintainers; [ happy-river erictapen ];
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index eadf8b62b9779..fef5dc82de042 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/nixos/modules/services/web-apps/matomo.nix
@@ -36,16 +36,7 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        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.
-        '';
-        default = pkgs.matomo;
-        defaultText = literalExpression "pkgs.matomo";
-      };
+      package = mkPackageOption pkgs "matomo" { };
 
       webServerUser = mkOption {
         type = types.nullOr types.str;
diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix
index 66e5f1695a155..5035594323749 100644
--- a/nixos/modules/services/web-apps/mattermost.nix
+++ b/nixos/modules/services/web-apps/mattermost.nix
@@ -102,12 +102,7 @@ in
     services.mattermost = {
       enable = mkEnableOption (lib.mdDoc "Mattermost chat server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mattermost;
-        defaultText = lib.literalExpression "pkgs.mattermost";
-        description = lib.mdDoc "Mattermost derivation to use.";
-      };
+      package = mkPackageOption pkgs "mattermost" { };
 
       statePath = mkOption {
         type = types.str;
@@ -250,12 +245,7 @@ in
 
       matterircd = {
         enable = mkEnableOption (lib.mdDoc "Mattermost IRC bridge");
-        package = mkOption {
-          type = types.package;
-          default = pkgs.matterircd;
-          defaultText = lib.literalExpression "pkgs.matterircd";
-          description = lib.mdDoc "matterircd derivation to use.";
-        };
+        package = mkPackageOption pkgs "matterircd" { };
         parameters = mkOption {
           type = types.listOf types.str;
           default = [ ];
@@ -287,9 +277,9 @@ in
 
       # The systemd service will fail to execute the preStart hook
       # if the WorkingDirectory does not exist
-      system.activationScripts.mattermost = ''
-        mkdir -p "${cfg.statePath}"
-      '';
+      systemd.tmpfiles.rules = [
+        ''d "${cfg.statePath}" -''
+      ];
 
       systemd.services.mattermost = {
         description = "Mattermost chat service";
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index 21c587694c6e2..5549b6ae1eaa4 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -2,34 +2,39 @@
 
 let
 
-  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
+  inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption;
   inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionals optionalString types;
 
   cfg = config.services.mediawiki;
   fpm = config.services.phpfpm.pools.mediawiki;
   user = "mediawiki";
-  group = if cfg.webserver == "apache" then config.services.httpd.group else "mediawiki";
+  group =
+    if cfg.webserver == "apache" then
+      config.services.httpd.group
+    else if cfg.webserver == "nginx" then
+      config.services.nginx.group
+    else "mediawiki";
 
   cacheDir = "/var/cache/mediawiki";
   stateDir = "/var/lib/mediawiki";
 
   pkg = pkgs.stdenv.mkDerivation rec {
     pname = "mediawiki-full";
-    version = src.version;
+    inherit (src) version;
     src = cfg.package;
 
     installPhase = ''
       mkdir -p $out
       cp -r * $out/
 
-      rm -rf $out/share/mediawiki/skins/*
-      rm -rf $out/share/mediawiki/extensions/*
-
+      # try removing directories before symlinking to allow overwriting any builtin extension or skin
       ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+        rm -rf $out/share/mediawiki/skins/${k}
         ln -s ${v} $out/share/mediawiki/skins/${k}
       '') cfg.skins)}
 
       ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+        rm -rf $out/share/mediawiki/extensions/${k}
         ln -s ${if v != null then v else "$src/share/mediawiki/extensions/${k}"} $out/share/mediawiki/extensions/${k}
       '') cfg.extensions)}
     '';
@@ -71,7 +76,7 @@ let
       ## For more information on customizing the URLs
       ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
       ## https://www.mediawiki.org/wiki/Manual:Short_URL
-      $wgScriptPath = "";
+      $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}";
 
       ## The protocol and server name to use in fully-qualified URLs
       $wgServer = "${cfg.url}";
@@ -79,6 +84,11 @@ let
       ## The URL path to static resources (images, scripts, etc.)
       $wgResourceBasePath = $wgScriptPath;
 
+      ${lib.optionalString (cfg.webserver == "nginx") ''
+        $wgArticlePath = "/wiki/$1";
+        $wgUsePathInfo = true;
+      ''}
+
       ## The URL path to the logo.  Make sure you change this from the default,
       ## or else you'll overwrite your logo when you upgrade!
       $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
@@ -175,6 +185,7 @@ let
       ${cfg.extraConfig}
   '';
 
+  withTrailingSlash = str: if lib.hasSuffix "/" str then str else "${str}/";
 in
 {
   # interface
@@ -183,12 +194,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "MediaWiki");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mediawiki;
-        defaultText = literalExpression "pkgs.mediawiki";
-        description = lib.mdDoc "Which MediaWiki package to use.";
-      };
+      package = mkPackageOption pkgs "mediawiki" { };
 
       finalPackage = mkOption {
         type = types.package;
@@ -209,15 +215,18 @@ in
 
       url = mkOption {
         type = types.str;
-        default = if cfg.webserver == "apache" then
-            "${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://${cfg.httpd.virtualHost.hostName}"
-          else
-            "http://localhost";
-        defaultText = literalExpression ''
+        default =
           if cfg.webserver == "apache" then
-            "''${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://''${cfg.httpd.virtualHost.hostName}"
+            "${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://${cfg.httpd.virtualHost.hostName}"
+          else if cfg.webserver == "nginx" then
+            let
+              hasSSL = host: host.forceSSL || host.addSSL;
+            in
+            "${if hasSSL config.services.nginx.virtualHosts.${cfg.nginx.hostName} then "https" else "http"}://${cfg.nginx.hostName}"
           else
             "http://localhost";
+        defaultText = ''
+          if "mediawiki uses ssl" then "{"https" else "http"}://''${cfg.hostName}" else "http://localhost";
         '';
         example = "https://wiki.example.org";
         description = lib.mdDoc "URL of the wiki.";
@@ -286,14 +295,14 @@ in
       };
 
       webserver = mkOption {
-        type = types.enum [ "apache" "none" ];
+        type = types.enum [ "apache" "none" "nginx" ];
         default = "apache";
         description = lib.mdDoc "Webserver to use.";
       };
 
       database = {
         type = mkOption {
-          type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ];
+          type = types.enum [ "mysql" "postgres" "mssql" "oracle" ];
           default = "mysql";
           description = lib.mdDoc "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers.";
         };
@@ -368,6 +377,16 @@ in
         };
       };
 
+      nginx.hostName = mkOption {
+        type = types.str;
+        example = literalExpression ''wiki.example.com'';
+        default = "localhost";
+        description = lib.mdDoc ''
+          The hostname to use for the nginx virtual host.
+          This is used to generate the nginx configuration.
+        '';
+      };
+
       httpd.virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
         example = literalExpression ''
@@ -427,7 +446,7 @@ in
       { assertion = cfg.database.createLocally -> (cfg.database.type == "mysql" || cfg.database.type == "postgres");
         message = "services.mediawiki.createLocally is currently only supported for database type 'mysql' and 'postgres'";
       }
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.name == cfg.database.user;
         message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.socket != null;
@@ -459,16 +478,21 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [{
         name = cfg.database.user;
-        ensurePermissions = { "DATABASE \"${cfg.database.name}\"" = "ALL PRIVILEGES"; };
+        ensureDBOwnership = true;
       }];
     };
 
     services.phpfpm.pools.mediawiki = {
       inherit user group;
       phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}";
+      # https://www.mediawiki.org/wiki/Compatibility
+      phpPackage = pkgs.php81;
       settings = (if (cfg.webserver == "apache") then {
         "listen.owner" = config.services.httpd.user;
         "listen.group" = config.services.httpd.group;
+      } else if (cfg.webserver == "nginx") then {
+        "listen.owner" = config.services.nginx.user;
+        "listen.group" = config.services.nginx.group;
       } else {
         "listen.owner" = user;
         "listen.group" = group;
@@ -503,6 +527,57 @@ in
         }
       ];
     };
+    # inspired by https://www.mediawiki.org/wiki/Manual:Short_URL/Nginx
+    services.nginx = lib.mkIf (cfg.webserver == "nginx") {
+      enable = true;
+      virtualHosts.${config.services.mediawiki.nginx.hostName} = {
+        root = "${pkg}/share/mediawiki";
+        locations = {
+          "~ ^/w/(index|load|api|thumb|opensearch_desc|rest|img_auth)\\.php$".extraConfig = ''
+            rewrite ^/w/(.*) /$1 break;
+            include ${config.services.nginx.package}/conf/fastcgi.conf;
+            fastcgi_index index.php;
+            fastcgi_pass unix:${config.services.phpfpm.pools.mediawiki.socket};
+          '';
+          "/w/images/".alias = withTrailingSlash cfg.uploadsDir;
+          # Deny access to deleted images folder
+          "/w/images/deleted".extraConfig = ''
+            deny all;
+          '';
+          # MediaWiki assets (usually images)
+          "~ ^/w/resources/(assets|lib|src)".extraConfig = ''
+            rewrite ^/w(/.*) $1 break;
+            add_header Cache-Control "public";
+            expires 7d;
+          '';
+          # Assets, scripts and styles from skins and extensions
+          "~ ^/w/(skins|extensions)/.+\\.(css|js|gif|jpg|jpeg|png|svg|wasm|ttf|woff|woff2)$".extraConfig = ''
+            rewrite ^/w(/.*) $1 break;
+            add_header Cache-Control "public";
+            expires 7d;
+          '';
+
+          # Handling for Mediawiki REST API, see [[mw:API:REST_API]]
+          "/w/rest.php/".tryFiles = "$uri $uri/ /w/rest.php?$query_string";
+
+          # Handling for the article path (pretty URLs)
+          "/wiki/".extraConfig = ''
+            rewrite ^/wiki/(?<pagename>.*)$ /w/index.php;
+          '';
+
+          # Explicit access to the root website, redirect to main page (adapt as needed)
+          "= /".extraConfig = ''
+            return 301 /wiki/;
+          '';
+
+          # Every other entry point will be disallowed.
+          # Add specific rules for other entry points/images as needed above this
+          "/".extraConfig = ''
+             return 404;
+          '';
+        };
+      };
+    };
 
     systemd.tmpfiles.rules = [
       "d '${stateDir}' 0750 ${user} ${group} - -"
@@ -527,15 +602,15 @@ in
         ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \
           --confpath /tmp \
           --scriptpath / \
-          --dbserver "${dbAddr}" \
+          --dbserver ${lib.escapeShellArg dbAddr} \
           --dbport ${toString cfg.database.port} \
-          --dbname ${cfg.database.name} \
-          ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \
-          --dbuser ${cfg.database.user} \
-          ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \
-          --passfile ${cfg.passwordFile} \
+          --dbname ${lib.escapeShellArg cfg.database.name} \
+          ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${lib.escapeShellArg cfg.database.tablePrefix}"} \
+          --dbuser ${lib.escapeShellArg cfg.database.user} \
+          ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${lib.escapeShellArg cfg.database.passwordFile}"} \
+          --passfile ${lib.escapeShellArg cfg.passwordFile} \
           --dbtype ${cfg.database.type} \
-          ${cfg.name} \
+          ${lib.escapeShellArg cfg.name} \
           admin
 
         ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick
@@ -553,7 +628,7 @@ in
       ++ optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "postgres") "postgresql.service";
 
     users.users.${user} = {
-      group = group;
+      inherit group;
       isSystemUser = true;
     };
     users.groups.${group} = {};
diff --git a/nixos/modules/services/web-apps/meme-bingo-web.nix b/nixos/modules/services/web-apps/meme-bingo-web.nix
new file mode 100644
index 0000000000000..fe68bbecca57e
--- /dev/null
+++ b/nixos/modules/services/web-apps/meme-bingo-web.nix
@@ -0,0 +1,88 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkEnableOption mkPackageOption mkIf mkOption mdDoc types literalExpression;
+
+  cfg = config.services.meme-bingo-web;
+in {
+  options = {
+    services.meme-bingo-web = {
+      enable = mkEnableOption (mdDoc ''
+        a web app for the meme bingo, rendered entirely on the web server and made interactive with forms.
+
+        Note: The application's author suppose to run meme-bingo-web behind a reverse proxy for SSL and HTTP/3
+      '');
+
+      package = mkPackageOption pkgs "meme-bingo-web" { };
+
+      baseUrl = mkOption {
+        description = mdDoc ''
+          URL to be used for the HTML <base> element on all HTML routes.
+        '';
+        type = types.str;
+        default = "http://localhost:41678/";
+        example = "https://bingo.example.com/";
+      };
+      port = mkOption {
+        description = mdDoc ''
+          Port to be used for the web server.
+        '';
+        type = types.port;
+        default = 41678;
+        example = 21035;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.meme-bingo-web = {
+      description = "A web app for playing meme bingos.";
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        MEME_BINGO_BASE = cfg.baseUrl;
+        MEME_BINGO_PORT = toString cfg.port;
+      };
+      path = [ cfg.package ];
+
+      serviceConfig = {
+        User = "meme-bingo-web";
+        Group = "meme-bingo-web";
+
+        DynamicUser = true;
+
+        ExecStart = "${cfg.package}/bin/meme-bingo-web";
+
+        Restart = "always";
+        RestartSec = 1;
+
+        # Hardening
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "/dev/random" ];
+        LockPersonality = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectSystem = "strict";
+        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";
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+        NoNewPrivileges = true;
+        MemoryDenyWriteExecute = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/microbin.nix b/nixos/modules/services/web-apps/microbin.nix
new file mode 100644
index 0000000000000..233bfac6e6995
--- /dev/null
+++ b/nixos/modules/services/web-apps/microbin.nix
@@ -0,0 +1,93 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.microbin;
+in
+{
+  options.services.microbin = {
+    enable = lib.mkEnableOption (lib.mdDoc "MicroBin is a super tiny, feature rich, configurable paste bin web application");
+
+    package = lib.mkPackageOption pkgs "microbin" { };
+
+    settings = lib.mkOption {
+      type = lib.types.submodule { freeformType = with lib.types; attrsOf (oneOf [ bool int str ]); };
+      default = { };
+      example = {
+        MICROBIN_PORT = 8080;
+        MICROBIN_HIDE_LOGO = false;
+      };
+      description = lib.mdDoc ''
+        Additional configuration for MicroBin, see
+        <https://microbin.eu/docs/installation-and-configuration/configuration/>
+        for supported values.
+
+        For secrets use passwordFile option instead.
+      '';
+    };
+
+    dataDir = lib.mkOption {
+      type = lib.types.str;
+      default = "/var/lib/microbin";
+      description = lib.mdDoc "Default data folder for MicroBin.";
+    };
+
+    passwordFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      example = "/run/secrets/microbin.env";
+      description = lib.mdDoc ''
+        Path to file containing environment variables.
+        Useful for passing down secrets.
+        Variables that can be considered secrets are:
+         - MICROBIN_BASIC_AUTH_USERNAME
+         - MICROBIN_BASIC_AUTH_PASSWORD
+         - MICROBIN_ADMIN_USERNAME
+         - MICROBIN_ADMIN_PASSWORD
+         - MICROBIN_UPLOADER_PASSWORD
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.microbin.settings = with lib; {
+      MICROBIN_BIND = mkDefault "0.0.0.0";
+      MICROBIN_DISABLE_TELEMETRY = mkDefault true;
+      MICROBIN_LIST_SERVER = mkDefault false;
+      MICROBIN_PORT = mkDefault "8080";
+    };
+
+    systemd.services.microbin = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = lib.mapAttrs (_: v: if lib.isBool v then lib.boolToString v else toString v) cfg.settings;
+      serviceConfig = {
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        EnvironmentFile = lib.optional (cfg.passwordFile != null) cfg.passwordFile;
+        ExecStart = "${cfg.package}/bin/microbin";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ReadWritePaths = cfg.dataDir;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        StateDirectory = "microbin";
+        SystemCallArchitectures = [ "native" ];
+        SystemCallFilter = [ "@system-service" ];
+        WorkingDirectory = cfg.dataDir;
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ surfaceflinger ];
+}
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 3374c746ad3dd..1a5b7d0c24e9b 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -6,13 +6,10 @@ let
 
   defaultAddress = "localhost:8080";
 
-  dbUser = "miniflux";
-  dbName = "miniflux";
-
   pgbin = "${config.services.postgresql.package}/bin";
   preStart = pkgs.writeScript "miniflux-pre-start" ''
     #!${pkgs.runtimeShell}
-    ${pgbin}/psql "${dbName}" -c "CREATE EXTENSION IF NOT EXISTS hstore"
+    ${pgbin}/psql "miniflux" -c "CREATE EXTENSION IF NOT EXISTS hstore"
   '';
 in
 
@@ -21,18 +18,13 @@ in
     services.miniflux = {
       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.";
-      };
+      package = mkPackageOption pkgs "miniflux" { };
 
       config = mkOption {
-        type = types.attrsOf types.str;
+        type = with types; attrsOf (oneOf [ str int ]);
         example = literalExpression ''
           {
-            CLEANUP_FREQUENCY = "48";
+            CLEANUP_FREQUENCY = 48;
             LISTEN_ADDR = "localhost:8080";
           }
         '';
@@ -59,23 +51,20 @@ in
   };
 
   config = mkIf cfg.enable {
-
     services.miniflux.config =  {
       LISTEN_ADDR = mkDefault defaultAddress;
-      DATABASE_URL = "user=${dbUser} host=/run/postgresql dbname=${dbName}";
-      RUN_MIGRATIONS = "1";
-      CREATE_ADMIN = "1";
+      DATABASE_URL = "user=miniflux host=/run/postgresql dbname=miniflux";
+      RUN_MIGRATIONS = 1;
+      CREATE_ADMIN = 1;
     };
 
     services.postgresql = {
       enable = true;
       ensureUsers = [ {
-        name = dbUser;
-        ensurePermissions = {
-          "DATABASE ${dbName}" = "ALL PRIVILEGES";
-        };
+        name = "miniflux";
+        ensureDBOwnership = true;
       } ];
-      ensureDatabases = [ dbName ];
+      ensureDatabases = [ "miniflux" ];
     };
 
     systemd.services.miniflux-dbsetup = {
@@ -97,10 +86,10 @@ in
 
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/miniflux";
-        User = dbUser;
+        User = "miniflux";
         DynamicUser = true;
         RuntimeDirectory = "miniflux";
-        RuntimeDirectoryMode = "0700";
+        RuntimeDirectoryMode = "0750";
         EnvironmentFile = cfg.adminCredentialsFile;
         # Hardening
         CapabilityBoundingSet = [ "" ];
@@ -127,7 +116,7 @@ in
         UMask = "0077";
       };
 
-      environment = cfg.config;
+      environment = lib.mapAttrs (_: toString) cfg.config;
     };
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/services/web-apps/mobilizon.nix b/nixos/modules/services/web-apps/mobilizon.nix
new file mode 100644
index 0000000000000..bdb08f6131496
--- /dev/null
+++ b/nixos/modules/services/web-apps/mobilizon.nix
@@ -0,0 +1,449 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mobilizon;
+
+  user = "mobilizon";
+  group = "mobilizon";
+
+  settingsFormat = pkgs.formats.elixirConf { elixir = cfg.package.elixirPackage; };
+
+  configFile = settingsFormat.generate "mobilizon-config.exs" cfg.settings;
+
+  # Make a package containing launchers with the correct envirenment, instead of
+  # setting it with systemd services, so that the user can also use them without
+  # troubles
+  launchers = pkgs.stdenv.mkDerivation rec {
+    pname = "${cfg.package.pname}-launchers";
+    inherit (cfg.package) version;
+
+    src = cfg.package;
+
+    nativeBuildInputs = with pkgs; [ makeWrapper ];
+
+    dontBuild = true;
+
+    installPhase = ''
+      mkdir -p $out/bin
+
+      makeWrapper \
+        $src/bin/mobilizon \
+        $out/bin/mobilizon \
+        --run '. ${secretEnvFile}' \
+        --set MOBILIZON_CONFIG_PATH "${configFile}" \
+        --set-default RELEASE_TMP "/tmp"
+
+      makeWrapper \
+        $src/bin/mobilizon_ctl \
+        $out/bin/mobilizon_ctl \
+        --run '. ${secretEnvFile}' \
+        --set MOBILIZON_CONFIG_PATH "${configFile}" \
+        --set-default RELEASE_TMP "/tmp"
+    '';
+  };
+
+  repoSettings = cfg.settings.":mobilizon"."Mobilizon.Storage.Repo";
+  instanceSettings = cfg.settings.":mobilizon".":instance";
+
+  isLocalPostgres = repoSettings.socket_dir != null;
+
+  dbUser = if repoSettings.username != null then repoSettings.username else "mobilizon";
+
+  postgresql = config.services.postgresql.package;
+  postgresqlSocketDir = "/var/run/postgresql";
+
+  secretEnvFile = "/var/lib/mobilizon/secret-env.sh";
+in
+{
+  options = {
+    services.mobilizon = {
+      enable = mkEnableOption
+        (lib.mdDoc "Mobilizon federated organization and mobilization platform");
+
+      nginx.enable = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Whether an Nginx virtual host should be
+          set up to serve Mobilizon.
+        '';
+      };
+
+      package = mkPackageOption pkgs "mobilizon" { };
+
+      settings = mkOption {
+        type =
+          let
+            elixirTypes = settingsFormat.lib.types;
+          in
+          types.submodule {
+            freeformType = settingsFormat.type;
+
+            options = {
+              ":mobilizon" = {
+
+                "Mobilizon.Web.Endpoint" = {
+                  url.host = mkOption {
+                    type = elixirTypes.str;
+                    defaultText = lib.literalMD ''
+                      ''${settings.":mobilizon".":instance".hostname}
+                    '';
+                    description = lib.mdDoc ''
+                      Your instance's hostname for generating URLs throughout the app
+                    '';
+                  };
+
+                  http = {
+                    port = mkOption {
+                      type = elixirTypes.port;
+                      default = 4000;
+                      description = lib.mdDoc ''
+                        The port to run the server
+                      '';
+                    };
+                    ip = mkOption {
+                      type = elixirTypes.tuple;
+                      default = settingsFormat.lib.mkTuple [ 0 0 0 0 0 0 0 1 ];
+                      description = lib.mdDoc ''
+                        The IP address to listen on. Defaults to [::1] notated as a byte tuple.
+                      '';
+                    };
+                  };
+
+                  has_reverse_proxy = mkOption {
+                    type = elixirTypes.bool;
+                    default = true;
+                    description = lib.mdDoc ''
+                      Whether you use a reverse proxy
+                    '';
+                  };
+                };
+
+                ":instance" = {
+                  name = mkOption {
+                    type = elixirTypes.str;
+                    description = lib.mdDoc ''
+                      The fallback instance name if not configured into the admin UI
+                    '';
+                  };
+
+                  hostname = mkOption {
+                    type = elixirTypes.str;
+                    description = lib.mdDoc ''
+                      Your instance's hostname
+                    '';
+                  };
+
+                  email_from = mkOption {
+                    type = elixirTypes.str;
+                    defaultText = literalExpression ''
+                      noreply@''${settings.":mobilizon".":instance".hostname}
+                    '';
+                    description = lib.mdDoc ''
+                      The email for the From: header in emails
+                    '';
+                  };
+
+                  email_reply_to = mkOption {
+                    type = elixirTypes.str;
+                    defaultText = literalExpression ''
+                      ''${email_from}
+                    '';
+                    description = lib.mdDoc ''
+                      The email for the Reply-To: header in emails
+                    '';
+                  };
+                };
+
+                "Mobilizon.Storage.Repo" = {
+                  socket_dir = mkOption {
+                    type = types.nullOr elixirTypes.str;
+                    default = postgresqlSocketDir;
+                    description = lib.mdDoc ''
+                      Path to the postgres socket directory.
+
+                      Set this to null if you want to connect to a remote database.
+
+                      If non-null, the local PostgreSQL server will be configured with
+                      the configured database, permissions, and required extensions.
+
+                      If connecting to a remote database, please follow the
+                      instructions on how to setup your database:
+                      <https://docs.joinmobilizon.org/administration/install/release/#database-setup>
+                    '';
+                  };
+
+                  username = mkOption {
+                    type = types.nullOr elixirTypes.str;
+                    default = user;
+                    description = lib.mdDoc ''
+                      User used to connect to the database
+                    '';
+                  };
+
+                  database = mkOption {
+                    type = types.nullOr elixirTypes.str;
+                    default = "mobilizon_prod";
+                    description = lib.mdDoc ''
+                      Name of the database
+                    '';
+                  };
+                };
+              };
+            };
+          };
+        default = { };
+
+        description = lib.mdDoc ''
+          Mobilizon Elixir documentation, see
+          <https://docs.joinmobilizon.org/administration/configure/reference/>
+          for supported values.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = cfg.nginx.enable -> (cfg.settings.":mobilizon"."Mobilizon.Web.Endpoint".http.ip == settingsFormat.lib.mkTuple [ 0 0 0 0 0 0 0 1 ]);
+        message = "Setting the IP mobilizon listens on is only possible when the nginx config is not used, as it is hardcoded there.";
+      }
+    ];
+
+    services.mobilizon.settings = {
+      ":mobilizon" = {
+        "Mobilizon.Web.Endpoint" = {
+          server = true;
+          url.host = mkDefault instanceSettings.hostname;
+          secret_key_base =
+            settingsFormat.lib.mkGetEnv { envVariable = "MOBILIZON_INSTANCE_SECRET"; };
+        };
+
+        "Mobilizon.Web.Auth.Guardian".secret_key =
+          settingsFormat.lib.mkGetEnv { envVariable = "MOBILIZON_AUTH_SECRET"; };
+
+        ":instance" = {
+          registrations_open = mkDefault false;
+          demo = mkDefault false;
+          email_from = mkDefault "noreply@${instanceSettings.hostname}";
+          email_reply_to = mkDefault instanceSettings.email_from;
+        };
+
+        "Mobilizon.Storage.Repo" = {
+          # Forced by upstream since it uses PostgreSQL-specific extensions
+          adapter = settingsFormat.lib.mkAtom "Ecto.Adapters.Postgres";
+          pool_size = mkDefault 10;
+        };
+      };
+
+      ":tzdata".":data_dir" = "/var/lib/mobilizon/tzdata/";
+    };
+
+    # This somewhat follows upstream's systemd service here:
+    # https://framagit.org/framasoft/mobilizon/-/blob/master/support/systemd/mobilizon.service
+    systemd.services.mobilizon = {
+      description = "Mobilizon federated organization and mobilization platform";
+
+      wantedBy = [ "multi-user.target" ];
+
+      path = with pkgs; [
+        gawk
+        imagemagick
+        libwebp
+        file
+
+        # Optional:
+        gifsicle
+        jpegoptim
+        optipng
+        pngquant
+      ];
+
+      serviceConfig = {
+        ExecStartPre = "${launchers}/bin/mobilizon_ctl migrate";
+        ExecStart = "${launchers}/bin/mobilizon start";
+        ExecStop = "${launchers}/bin/mobilizon stop";
+
+        User = user;
+        Group = group;
+
+        StateDirectory = "mobilizon";
+
+        Restart = "on-failure";
+
+        PrivateTmp = true;
+        ProtectSystem = "full";
+        NoNewPrivileges = true;
+
+        ReadWritePaths = mkIf isLocalPostgres postgresqlSocketDir;
+      };
+    };
+
+    # Create the needed secrets before running Mobilizon, so that they are not
+    # in the nix store
+    #
+    # Since some of these tasks are quite common for Elixir projects (COOKIE for
+    # every BEAM project, Phoenix and Guardian are also quite common), this
+    # service could be abstracted in the future, and used by other Elixir
+    # projects.
+    systemd.services.mobilizon-setup-secrets = {
+      description = "Mobilizon setup secrets";
+      before = [ "mobilizon.service" ];
+      wantedBy = [ "mobilizon.service" ];
+
+      script =
+        let
+          # Taken from here:
+          # https://framagit.org/framasoft/mobilizon/-/blob/1.0.7/lib/mix/tasks/mobilizon/instance.ex#L132-133
+          genSecret =
+            "IO.puts(:crypto.strong_rand_bytes(64)" +
+            "|> Base.encode64()" +
+            "|> binary_part(0, 64))";
+
+          # Taken from here:
+          # https://github.com/elixir-lang/elixir/blob/v1.11.3/lib/mix/lib/mix/release.ex#L499
+          genCookie = "IO.puts(Base.encode32(:crypto.strong_rand_bytes(32)))";
+
+          evalElixir = str: ''
+            ${cfg.package.elixirPackage}/bin/elixir --eval '${str}'
+          '';
+        in
+        ''
+          set -euxo pipefail
+
+          if [ ! -f "${secretEnvFile}" ]; then
+            install -m 600 /dev/null "${secretEnvFile}"
+            cat > "${secretEnvFile}" <<EOF
+          # This file was automatically generated by mobilizon-setup-secrets.service
+          export MOBILIZON_AUTH_SECRET='$(${evalElixir genSecret})'
+          export MOBILIZON_INSTANCE_SECRET='$(${evalElixir genSecret})'
+          export RELEASE_COOKIE='$(${evalElixir genCookie})'
+          EOF
+          fi
+        '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        Group = group;
+        StateDirectory = "mobilizon";
+      };
+    };
+
+    # Add the required PostgreSQL extensions to the local PostgreSQL server,
+    # if local PostgreSQL is configured.
+    systemd.services.mobilizon-postgresql = mkIf isLocalPostgres {
+      description = "Mobilizon PostgreSQL setup";
+
+      after = [ "postgresql.service" ];
+      before = [ "mobilizon.service" "mobilizon-setup-secrets.service" ];
+      wantedBy = [ "mobilizon.service" ];
+
+      path = [ postgresql ];
+
+      # Taken from here:
+      # https://framagit.org/framasoft/mobilizon/-/blob/1.1.0/priv/templates/setup_db.eex
+      # TODO(to maintainers of mobilizon): the owner database alteration is necessary
+      # as PostgreSQL 15 changed their behaviors w.r.t. to privileges.
+      # See https://github.com/NixOS/nixpkgs/issues/216989 to get rid
+      # of that workaround.
+      script =
+        ''
+          psql "${repoSettings.database}" -c "\
+            CREATE EXTENSION IF NOT EXISTS postgis; \
+            CREATE EXTENSION IF NOT EXISTS pg_trgm; \
+            CREATE EXTENSION IF NOT EXISTS unaccent;"
+          psql -tAc 'ALTER DATABASE "${repoSettings.database}" OWNER TO "${dbUser}";'
+
+        '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = config.services.postgresql.superUser;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d /var/lib/mobilizon/uploads/exports/csv 700 mobilizon mobilizon - -"
+      "Z /var/lib/mobilizon 700 mobilizon mobilizon - -"
+    ];
+
+    services.postgresql = mkIf isLocalPostgres {
+      enable = true;
+      ensureDatabases = [ repoSettings.database ];
+      ensureUsers = [
+        {
+          name = dbUser;
+          # Given that `dbUser` is potentially arbitrarily custom, we will perform
+          # manual fixups in mobilizon-postgres.
+          # TODO(to maintainers of mobilizon): Feel free to simplify your setup by using `ensureDBOwnership`.
+          ensureDBOwnership = false;
+        }
+      ];
+      extraPlugins = ps: with ps; [ postgis ];
+    };
+
+    # Nginx config taken from support/nginx/mobilizon-release.conf
+    services.nginx =
+      let
+        inherit (cfg.settings.":mobilizon".":instance") hostname;
+        proxyPass = "http://[::1]:"
+          + toString cfg.settings.":mobilizon"."Mobilizon.Web.Endpoint".http.port;
+      in
+      lib.mkIf cfg.nginx.enable {
+        enable = true;
+        virtualHosts."${hostname}" = {
+          enableACME = lib.mkDefault true;
+          forceSSL = lib.mkDefault true;
+          extraConfig = ''
+            proxy_http_version 1.1;
+            proxy_set_header Upgrade $http_upgrade;
+            proxy_set_header Connection "upgrade";
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+          '';
+          locations."/" = {
+            inherit proxyPass;
+          };
+          locations."~ ^/(js|css|img)" = {
+            root = "${cfg.package}/lib/mobilizon-${cfg.package.version}/priv/static";
+            extraConfig = ''
+              etag off;
+              access_log off;
+              add_header Cache-Control "public, max-age=31536000, immutable";
+            '';
+          };
+          locations."~ ^/(media|proxy)" = {
+            inherit proxyPass;
+            extraConfig = ''
+              etag off;
+              access_log off;
+              add_header Cache-Control "public, max-age=31536000, immutable";
+            '';
+          };
+        };
+      };
+
+    users.users.${user} = {
+      description = "Mobilizon daemon user";
+      group = group;
+      isSystemUser = true;
+    };
+
+    users.groups.${group} = { };
+
+    # So that we have the `mobilizon` and `mobilizon_ctl` commands.
+    # The `mobilizon remote` command is useful for dropping a shell into the
+    # running Mobilizon instance, and `mobilizon_ctl` is used for common
+    # management tasks (e.g. adding users).
+    environment.systemPackages = [ launchers ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ minijackson erictapen ];
+}
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
index b617e9a593795..ce6a800547255 100644
--- a/nixos/modules/services/web-apps/moodle.nix
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption types;
   inherit (lib) concatStringsSep literalExpression mapAttrsToList optional optionalString;
 
   cfg = config.services.moodle;
@@ -66,12 +66,7 @@ in
   options.services.moodle = {
     enable = mkEnableOption (lib.mdDoc "Moodle web application");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.moodle;
-      defaultText = literalExpression "pkgs.moodle";
-      description = lib.mdDoc "The Moodle package to use.";
-    };
+    package = mkPackageOption pkgs "moodle" { };
 
     initialPassword = mkOption {
       type = types.str;
@@ -194,7 +189,7 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+      { assertion = cfg.database.createLocally -> cfg.database.user == user && cfg.database.user == cfg.database.name;
         message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true";
       }
       { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
@@ -220,7 +215,7 @@ in
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
         { name = cfg.database.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/netbox.nix b/nixos/modules/services/web-apps/netbox.nix
index 6d89ffc2a7b70..d034f3234a2bd 100644
--- a/nixos/modules/services/web-apps/netbox.nix
+++ b/nixos/modules/services/web-apps/netbox.nix
@@ -74,9 +74,22 @@ in {
 
     package = lib.mkOption {
       type = lib.types.package;
-      default = if lib.versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+      default =
+        if lib.versionAtLeast config.system.stateVersion "24.05"
+        then pkgs.netbox_3_7
+        else if lib.versionAtLeast config.system.stateVersion "23.11"
+        then pkgs.netbox_3_6
+        else if lib.versionAtLeast config.system.stateVersion "23.05"
+        then pkgs.netbox_3_5
+        else pkgs.netbox_3_3;
       defaultText = lib.literalExpression ''
-        if versionAtLeast config.system.stateVersion "23.05" then pkgs.netbox else pkgs.netbox_3_3;
+        if lib.versionAtLeast config.system.stateVersion "24.05"
+        then pkgs.netbox_3_7
+        else if lib.versionAtLeast config.system.stateVersion "23.11"
+        then pkgs.netbox_3_6
+        else if lib.versionAtLeast config.system.stateVersion "23.05"
+        then pkgs.netbox_3_5
+        else pkgs.netbox_3_3;
       '';
       description = lib.mdDoc ''
         NetBox package to use.
@@ -248,9 +261,7 @@ in {
       ensureUsers = [
         {
           name = "netbox";
-          ensurePermissions = {
-            "DATABASE netbox" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
@@ -260,6 +271,7 @@ in {
     systemd.targets.netbox = {
       description = "Target for all NetBox services";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" "redis-netbox.service" ];
     };
 
@@ -298,19 +310,20 @@ in {
           ${pkg}/bin/netbox trace_paths --no-input
           ${pkg}/bin/netbox collectstatic --no-input
           ${pkg}/bin/netbox remove_stale_contenttypes --no-input
-          # TODO: remove the condition when we remove netbox_3_3
-          ${lib.optionalString
-            (lib.versionAtLeast cfg.package.version "3.5.0")
-            "${pkg}/bin/netbox reindex --lazy"}
+          ${pkg}/bin/netbox reindex --lazy
           ${pkg}/bin/netbox clearsessions
-          ${pkg}/bin/netbox clearcache
+          ${lib.optionalString
+            # The clearcache command was removed in 3.7.0:
+            # https://github.com/netbox-community/netbox/issues/14458
+            (lib.versionOlder cfg.package.version "3.7.0")
+            "${pkg}/bin/netbox clearcache"}
 
           echo "${cfg.package.version}" > "$versionFile"
         '';
 
         serviceConfig = defaultServiceConfig // {
           ExecStart = ''
-            ${pkgs.python3Packages.gunicorn}/bin/gunicorn netbox.wsgi \
+            ${pkg.gunicorn}/bin/gunicorn netbox.wsgi \
               --bind ${cfg.listenAddress}:${toString cfg.port} \
               --pythonpath ${pkg}/opt/netbox/netbox
           '';
diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md
index cbd7b5b3d066b..ce8f96a6a3896 100644
--- a/nixos/modules/services/web-apps/nextcloud.md
+++ b/nixos/modules/services/web-apps/nextcloud.md
@@ -5,7 +5,7 @@ self-hostable cloud platform. The server setup can be automated using
 [services.nextcloud](#opt-services.nextcloud.enable). A
 desktop client is packaged at `pkgs.nextcloud-client`.
 
-The current default by NixOS is `nextcloud27` which is also the latest
+The current default by NixOS is `nextcloud28` which is also the latest
 major version available.
 
 ## Basic usage {#module-services-nextcloud-basic-usage}
@@ -49,9 +49,9 @@ used by the imperative installer and all values are written to an additional fil
 to ensure that changes can be applied by changing the module's options.
 
 In case the application serves multiple domains (those are checked with
-[`$_SERVER['HTTP_HOST']`](http://php.net/manual/en/reserved.variables.server.php))
+[`$_SERVER['HTTP_HOST']`](https://www.php.net/manual/en/reserved.variables.server.php))
 it's needed to add them to
-[`services.nextcloud.config.extraTrustedDomains`](#opt-services.nextcloud.config.extraTrustedDomains).
+[`services.nextcloud.extraOptions.trusted_domains`](#opt-services.nextcloud.extraOptions.trusted_domains).
 
 Auto updates for Nextcloud apps can be enabled using
 [`services.nextcloud.autoUpdateApps`](#opt-services.nextcloud.autoUpdateApps.enable).
@@ -119,13 +119,7 @@ Auto updates for Nextcloud apps can be enabled using
   - **Server-side encryption.**
     Nextcloud supports [server-side encryption (SSE)](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html).
     This is not an end-to-end encryption, but can be used to encrypt files that will be persisted
-    to external storage such as S3. Please note that this won't work anymore when using OpenSSL 3
-    for PHP's openssl extension and **Nextcloud 25 or older** because this is implemented using the
-    legacy cipher RC4. For Nextcloud26 this isn't relevant anymore, because Nextcloud has an RC4 implementation
-    written in native PHP and thus doesn't need `ext-openssl` for that anymore.
-    If [](#opt-system.stateVersion) is *above* `22.05`,
-    this is disabled by default. To turn it on again and for further information please refer to
-    [](#opt-services.nextcloud.enableBrokenCiphersForSSE).
+    to external storage such as S3.
 
 ## Using an alternative webserver as reverse-proxy (e.g. `httpd`) {#module-services-nextcloud-httpd}
 
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index e0a7e7d4859c8..0b19265942c03 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -9,6 +9,7 @@ let
   jsonFormat = pkgs.formats.json {};
 
   defaultPHPSettings = {
+    output_buffering = "0";
     short_open_tag = "Off";
     expose_php = "Off";
     error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
@@ -23,17 +24,49 @@ let
     catch_workers_output = "yes";
   };
 
+  appStores = {
+    # default apps bundled with pkgs.nextcloudXX, e.g. files, contacts
+    apps = {
+      enabled = true;
+      writable = false;
+    };
+    # apps installed via cfg.extraApps
+    nix-apps = {
+      enabled = cfg.extraApps != { };
+      linkTarget = pkgs.linkFarm "nix-apps"
+        (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps);
+      writable = false;
+    };
+    # apps installed via the app store.
+    store-apps = {
+      enabled = cfg.appstoreEnable == null || cfg.appstoreEnable;
+      linkTarget = "${cfg.home}/store-apps";
+      writable = true;
+    };
+  };
+
+  webroot = pkgs.runCommand
+    "${cfg.package.name or "nextcloud"}-with-apps"
+    { }
+    ''
+      mkdir $out
+      ln -sfv "${cfg.package}"/* "$out"
+      ${concatStrings
+        (mapAttrsToList (name: store: optionalString (store.enabled && store?linkTarget) ''
+          if [ -e "$out"/${name} ]; then
+            echo "Didn't expect ${name} already in $out!"
+            exit 1
+          fi
+          ln -sfTv ${store.linkTarget} "$out"/${name}
+        '') appStores)}
+    '';
+
   inherit (cfg) datadir;
 
   phpPackage = cfg.phpPackage.buildEnv {
     extensions = { enabled, all }:
-      (with all;
-        # 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 ])
+      (with all; enabled
+        ++ [ bz2 intl sodium ] # recommended
         ++ optional cfg.enableImagemagick imagick
         # Optionally enabled depending on caching settings
         ++ optional cfg.caching.apcu apcu
@@ -50,7 +83,7 @@ let
 
   occ = pkgs.writeScriptBin "nextcloud-occ" ''
     #! ${pkgs.runtimeShell}
-    cd ${cfg.package}
+    cd ${webroot}
     sudo=exec
     if [[ "$USER" != nextcloud ]]; then
       sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS'
@@ -66,68 +99,132 @@ let
   mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
   pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
 
-in {
-
-  imports = [
-    (mkRemovedOptionModule [ "services" "nextcloud" "config" "adminpass" ] ''
-      Please use `services.nextcloud.config.adminpassFile' instead!
-    '')
-    (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbpass" ] ''
-      Please use `services.nextcloud.config.dbpassFile' instead!
-    '')
-    (mkRemovedOptionModule [ "services" "nextcloud" "nginx" "enable" ] ''
-      The nextcloud module supports `nginx` as reverse-proxy by default and doesn't
-      support other reverse-proxies officially.
+  nextcloudGreaterOrEqualThan = versionAtLeast cfg.package.version;
+  nextcloudOlderThan = versionOlder cfg.package.version;
+
+  # https://github.com/nextcloud/documentation/pull/11179
+  ocmProviderIsNotAStaticDirAnymore = nextcloudGreaterOrEqualThan "27.1.2"
+    || (nextcloudOlderThan "27.0.0" && nextcloudGreaterOrEqualThan "26.0.8");
+
+  overrideConfig = let
+    c = cfg.config;
+    requiresReadSecretFunction = c.dbpassFile != null || c.objectstore.s3.enable;
+    objectstoreConfig = let s3 = c.objectstore.s3; in optionalString s3.enable ''
+      'objectstore' => [
+        'class' => '\\OC\\Files\\ObjectStore\\S3',
+        'arguments' => [
+          'bucket' => '${s3.bucket}',
+          'autocreate' => ${boolToString s3.autocreate},
+          'key' => '${s3.key}',
+          'secret' => nix_read_secret('${s3.secretFile}'),
+          ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"}
+          ${optionalString (s3.port != null) "'port' => ${toString s3.port},"}
+          'use_ssl' => ${boolToString s3.useSsl},
+          ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
+          'use_path_style' => ${boolToString s3.usePathStyle},
+          ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"}
+        ],
+      ]
+    '';
+    showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != {};
+    renderedAppStoreSetting =
+      let
+        x = cfg.appstoreEnable;
+      in
+        if x == null then "false"
+        else boolToString x;
+    mkAppStoreConfig = name: { enabled, writable, ... }: optionalString enabled ''
+      [ 'path' => '${webroot}/${name}', 'url' => '/${name}', 'writable' => ${boolToString writable} ],
+    '';
+  in pkgs.writeText "nextcloud-config.php" ''
+    <?php
+    ${optionalString requiresReadSecretFunction ''
+      function nix_read_secret($file) {
+        if (!file_exists($file)) {
+          throw new \RuntimeException(sprintf(
+            "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to "
+            . "exist! Please make sure that the file exists and has appropriate "
+            . "permissions for user & group 'nextcloud'!",
+            $file
+          ));
+        }
+        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);
 
-      However it's possible to use an alternative reverse-proxy by
+      if (json_last_error() !== JSON_ERROR_NONE) {
+        throw new \RuntimeException(sprintf("Cannot decode %s, because: %s", $file, json_last_error_msg()));
+      }
 
-        * disabling nginx
-        * setting `listen.owner` & `listen.group` in the phpfpm-pool to a different value
+      return $decoded;
+    }
+    $CONFIG = [
+      'apps_paths' => [
+        ${concatStrings (mapAttrsToList mkAppStoreConfig appStores)}
+      ],
+      ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"}
+      ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
+      ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
+      ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
+      ${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}"
+          ),
+        ''
+      }
+      'dbtype' => '${c.dbtype}',
+      ${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!"
+      ));
+    ''}
+  '';
+in {
 
-      Further details about this can be found in the `Nextcloud`-section of the NixOS-manual
-      (which can be opened e.g. by running `nixos-help`).
+  imports = [
+    (mkRemovedOptionModule [ "services" "nextcloud" "enableBrokenCiphersForSSE" ] ''
+      This option has no effect since there's no supported Nextcloud version packaged here
+      using OpenSSL for RC4 SSE.
     '')
-    (mkRemovedOptionModule [ "services" "nextcloud" "disableImagemagick" ] ''
-      Use services.nextcloud.enableImagemagick instead.
+    (mkRemovedOptionModule [ "services" "nextcloud" "config" "dbport" ] ''
+      Add port to services.nextcloud.config.dbhost instead.
     '')
+    (mkRenamedOptionModule
+      [ "services" "nextcloud" "logLevel" ] [ "services" "nextcloud" "extraOptions" "loglevel" ])
+    (mkRenamedOptionModule
+      [ "services" "nextcloud" "logType" ] [ "services" "nextcloud" "extraOptions" "log_type" ])
+    (mkRenamedOptionModule
+      [ "services" "nextcloud" "config" "defaultPhoneRegion" ] [ "services" "nextcloud" "extraOptions" "default_phone_region" ])
+    (mkRenamedOptionModule
+      [ "services" "nextcloud" "config" "overwriteProtocol" ] [ "services" "nextcloud" "extraOptions" "overwriteprotocol" ])
+    (mkRenamedOptionModule
+      [ "services" "nextcloud" "skeletonDirectory" ] [ "services" "nextcloud" "extraOptions" "skeletondirectory" ])
+    (mkRenamedOptionModule
+      [ "services" "nextcloud" "globalProfiles" ] [ "services" "nextcloud" "extraOptions" "profile.enabled" ])
+    (mkRenamedOptionModule
+      [ "services" "nextcloud" "config" "extraTrustedDomains" ] [ "services" "nextcloud" "extraOptions" "trusted_domains" ])
+    (mkRenamedOptionModule
+      [ "services" "nextcloud" "config" "trustedProxies" ] [ "services" "nextcloud" "extraOptions" "trusted_proxies" ])
   ];
 
   options.services.nextcloud = {
     enable = mkEnableOption (lib.mdDoc "nextcloud");
 
-    enableBrokenCiphersForSSE = mkOption {
-      type = types.bool;
-      default = versionOlder stateVersion "22.11";
-      defaultText = literalExpression "versionOlder system.stateVersion \"22.11\"";
-      description = lib.mdDoc ''
-        This option enables using the OpenSSL PHP extension linked against OpenSSL 1.1
-        rather than latest OpenSSL (≥ 3), this is not recommended unless you need
-        it for server-side encryption (SSE). SSE uses the legacy RC4 cipher which is
-        considered broken for several years now. See also [RFC7465](https://datatracker.ietf.org/doc/html/rfc7465).
-
-        This cipher has been disabled in OpenSSL ≥ 3 and requires
-        a specific legacy profile to re-enable it.
-
-        If you deploy Nextcloud using OpenSSL ≥ 3 for PHP and have
-        server-side encryption configured, you will not be able to access
-        your files anymore. Enabling this option can restore access to your files.
-        Upon testing we didn't encounter any data corruption when turning
-        this on and off again, but this cannot be guaranteed for
-        each Nextcloud installation.
-
-        It is `true` by default for systems with a [](#opt-system.stateVersion) below
-        `22.11` to make sure that existing installations won't break on update. On newer
-        NixOS systems you have to explicitly enable it on your own.
-
-        Please note that this only provides additional value when using
-        external storage such as S3 since it's not an end-to-end encryption.
-        If this is not the case,
-        it is advised to [disable server-side encryption](https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption) and set this to `false`.
-
-        In the future, Nextcloud may move to AES-256-GCM, by then,
-        this option will be removed.
-      '';
-    };
     hostName = mkOption {
       type = types.str;
       description = lib.mdDoc "FQDN for the nextcloud instance.";
@@ -157,12 +254,7 @@ in {
       '';
       example = literalExpression ''
         {
-          maps = pkgs.fetchNextcloudApp {
-            name = "maps";
-            sha256 = "007y80idqg6b6zk6kjxg4vgw0z8fsxs9lajnv49vv1zjy6jx2i1i";
-            url = "https://github.com/nextcloud/maps/releases/download/v0.1.9/maps-0.1.9.tar.gz";
-            version = "0.1.9";
-          };
+          inherit (pkgs.nextcloud25Packages.apps) mail calendar contact;
           phonetrack = pkgs.fetchNextcloudApp {
             name = "phonetrack";
             sha256 = "0qf366vbahyl27p9mshfma1as4nvql6w75zy2zk5xwwbp343vsbc";
@@ -191,32 +283,6 @@ in {
         Set this 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 = lib.mdDoc ''
-        Log level value between 0 (DEBUG) and 4 (FATAL).
-
-        - 0 (debug): Log all activity.
-
-        - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors.
-
-        - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors.
-
-        - 3 (error): Log failed operations and fatal errors.
-
-        - 4 (fatal): Log only fatal errors that cause the server to stop.
-      '';
-    };
-    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;
@@ -225,15 +291,10 @@ in {
     package = mkOption {
       type = types.package;
       description = lib.mdDoc "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud25" "nextcloud26" "nextcloud27" ];
+      relatedPackages = [ "nextcloud26" "nextcloud27" "nextcloud28" ];
     };
-    phpPackage = mkOption {
-      type = types.package;
-      relatedPackages = [ "php81" "php82" ];
-      defaultText = "pkgs.php";
-      description = lib.mdDoc ''
-        PHP package to use for Nextcloud.
-      '';
+    phpPackage = mkPackageOption pkgs "php" {
+      example = "php82";
     };
 
     maxUploadSize = mkOption {
@@ -245,16 +306,6 @@ in {
       '';
     };
 
-    skeletonDirectory = mkOption {
-      default = "";
-      type = types.str;
-      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.
-      '';
-    };
-
     webfinger = mkOption {
       type = types.bool;
       default = false;
@@ -280,7 +331,7 @@ in {
     };
 
     phpOptions = mkOption {
-      type = types.attrsOf types.str;
+      type = with types; attrsOf (oneOf [ str int ]);
       defaultText = literalExpression (generators.toPretty { } defaultPHPSettings);
       description = lib.mdDoc ''
         Options for PHP's php.ini file for nextcloud.
@@ -354,7 +405,6 @@ in {
 
     };
 
-
     config = {
       dbtype = mkOption {
         type = types.enum [ "sqlite" "pgsql" "mysql" ];
@@ -385,18 +435,14 @@ in {
           else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock"
           else "localhost";
         defaultText = "localhost";
+        example = "localhost:5000";
         description = lib.mdDoc ''
-          Database host or socket path.
+          Database host (+port) or socket path.
           If [](#opt-services.nextcloud.database.createLocally) is true and
           [](#opt-services.nextcloud.config.dbtype) is either `pgsql` or `mysql`,
           defaults to the correct Unix socket instead.
         '';
       };
-      dbport = mkOption {
-        type = with types; nullOr (either int str);
-        default = null;
-        description = lib.mdDoc "Database port.";
-      };
       dbtableprefix = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -419,53 +465,6 @@ in {
           setup of Nextcloud by the systemd service `nextcloud-setup.service`.
         '';
       };
-
-      extraTrustedDomains = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = lib.mdDoc ''
-          Trusted domains from which the Nextcloud installation will be
-          accessible.  You don't need to add
-          `services.nextcloud.hostname` here.
-        '';
-      };
-
-      trustedProxies = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = lib.mdDoc ''
-          Trusted proxies to provide if the Nextcloud installation is being
-          proxied to secure against, e.g. spoofing.
-        '';
-      };
-
-      overwriteProtocol = mkOption {
-        type = types.nullOr (types.enum [ "http" "https" ]);
-        default = null;
-        example = "https";
-
-        description = lib.mdDoc ''
-          Force Nextcloud to always use HTTP or HTTPS i.e. for link generation.
-          Nextcloud uses the currently used protocol by default, but when
-          behind a reverse-proxy, it may use `http` for everything although
-          Nextcloud may be served via HTTPS.
-        '';
-      };
-
-      defaultPhoneRegion = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        example = "DE";
-        description = lib.mdDoc ''
-          An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html)
-          country code which replaces automatic phone-number detection
-          without a country code.
-
-          As an example, with `DE` set as the default phone region,
-          the `+49` prefix can be omitted for phone numbers.
-        '';
-      };
-
       objectstore = {
         s3 = {
           enable = mkEnableOption (lib.mdDoc ''
@@ -648,30 +647,109 @@ in {
         The nextcloud-occ program preconfigured to target this Nextcloud instance.
       '';
     };
-    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 “Show to everyone”
-        if this flag is enabled:
-        - About
-        - Full name
-        - Headline
-        - Organisation
-        - Profile picture
-        - Role
-        - Twitter
-        - Website
-
-        Only has an effect in Nextcloud 23 and later.
-      '';
-    };
 
     extraOptions = mkOption {
-      type = jsonFormat.type;
+      type = types.submodule {
+        freeformType = jsonFormat.type;
+        options = {
+
+          loglevel = mkOption {
+            type = types.ints.between 0 4;
+            default = 2;
+            description = lib.mdDoc ''
+              Log level value between 0 (DEBUG) and 4 (FATAL).
+
+              - 0 (debug): Log all activity.
+
+              - 1 (info): Log activity such as user logins and file activities, plus warnings, errors, and fatal errors.
+
+              - 2 (warn): Log successful operations, as well as warnings of potential problems, errors and fatal errors.
+
+              - 3 (error): Log failed operations and fatal errors.
+
+              - 4 (fatal): Log only fatal errors that cause the server to stop.
+            '';
+          };
+          log_type = 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.
+            '';
+          };
+          skeletondirectory = mkOption {
+            default = "";
+            type = types.str;
+            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.
+            '';
+          };
+          trusted_domains = mkOption {
+            type = types.listOf types.str;
+            default = [];
+            description = lib.mdDoc ''
+              Trusted domains, from which the nextcloud installation will be
+              accessible. You don't need to add
+              `services.nextcloud.hostname` here.
+            '';
+          };
+          trusted_proxies = mkOption {
+            type = types.listOf types.str;
+            default = [];
+            description = lib.mdDoc ''
+              Trusted proxies, to provide if the nextcloud installation is being
+              proxied to secure against e.g. spoofing.
+            '';
+          };
+          overwriteprotocol = mkOption {
+            type = types.enum [ "" "http" "https" ];
+            default = "";
+            example = "https";
+            description = lib.mdDoc ''
+              Force Nextcloud to always use HTTP or HTTPS i.e. for link generation.
+              Nextcloud uses the currently used protocol by default, but when
+              behind a reverse-proxy, it may use `http` for everything although
+              Nextcloud may be served via HTTPS.
+            '';
+          };
+          default_phone_region = mkOption {
+            default = "";
+            type = types.str;
+            example = "DE";
+            description = lib.mdDoc ''
+              An [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html)
+              country code which replaces automatic phone-number detection
+              without a country code.
+
+              As an example, with `DE` set as the default phone region,
+              the `+49` prefix can be omitted for phone numbers.
+            '';
+          };
+          "profile.enabled" = 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 “Show to everyone”
+              if this flag is enabled:
+              - About
+              - Full name
+              - Headline
+              - Organisation
+              - Profile picture
+              - Role
+              - Twitter
+              - Website
+              Only has an effect in Nextcloud 23 and later.
+            '';
+          };
+        };
+      };
       default = {};
       description = lib.mdDoc ''
         Extra options which should be appended to Nextcloud's config.php file.
@@ -719,7 +797,7 @@ in {
 
   config = mkIf cfg.enable (mkMerge [
     { warnings = let
-        latest = 27;
+        latest = 28;
         upgradeWarning = major: nixos:
           ''
             A legacy Nextcloud install (from before NixOS ${nixos}) may be installed.
@@ -741,27 +819,7 @@ in {
         ++ (optional (versionOlder cfg.package.version "25") (upgradeWarning 24 "22.11"))
         ++ (optional (versionOlder cfg.package.version "26") (upgradeWarning 25 "23.05"))
         ++ (optional (versionOlder cfg.package.version "27") (upgradeWarning 26 "23.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 (cfg.enableBrokenCiphersForSSE && versionAtLeast cfg.package.version "26") ''
-          Nextcloud26 supports RC4 without requiring legacy OpenSSL, so
-          `services.nextcloud.enableBrokenCiphersForSSE` can be set to `false`.
-        '');
+        ++ (optional (versionOlder cfg.package.version "28") (upgradeWarning 27 "24.05"));
 
       services.nextcloud.package = with pkgs;
         mkDefault (
@@ -771,15 +829,13 @@ in {
               nextcloud defined in an overlay, please set `services.nextcloud.package` to
               `pkgs.nextcloud`.
             ''
-          else if versionOlder stateVersion "22.11" then nextcloud24
           else if versionOlder stateVersion "23.05" then nextcloud25
           else if versionOlder stateVersion "23.11" then nextcloud26
-          else nextcloud27
+          else if versionOlder stateVersion "24.05" then nextcloud27
+          else nextcloud28
         );
 
-      services.nextcloud.phpPackage =
-        if versionOlder cfg.package.version "26" then pkgs.php81
-        else pkgs.php82;
+      services.nextcloud.phpPackage = pkgs.php82;
 
       services.nextcloud.phpOptions = mkMerge [
         (mapAttrs (const mkOptionDefault) defaultPHPSettings)
@@ -821,116 +877,23 @@ in {
         timerConfig.Unit = "nextcloud-cron.service";
       };
 
-      systemd.tmpfiles.rules = ["d ${cfg.home} 0750 nextcloud nextcloud"];
+      systemd.tmpfiles.rules = map (dir: "d ${dir} 0750 nextcloud nextcloud - -") [
+        "${cfg.home}"
+        "${datadir}/config"
+        "${datadir}/data"
+        "${cfg.home}/store-apps"
+      ] ++ [
+        "L+ ${datadir}/config/override.config.php - - - - ${overrideConfig}"
+      ];
 
       systemd.services = {
         # When upgrading the Nextcloud package, Nextcloud can report errors such as
         # "The files of the app [all apps in /var/lib/nextcloud/apps] were not replaced correctly"
         # Restarting phpfpm on Nextcloud package update fixes these issues (but this is a workaround).
-        phpfpm-nextcloud.restartTriggers = [ cfg.package ];
+        phpfpm-nextcloud.restartTriggers = [ webroot overrideConfig ];
 
         nextcloud-setup = let
           c = cfg.config;
-          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' => [
-              'class' => '\\OC\\Files\\ObjectStore\\S3',
-              'arguments' => [
-                'bucket' => '${s3.bucket}',
-                'autocreate' => ${boolToString s3.autocreate},
-                'key' => '${s3.key}',
-                'secret' => nix_read_secret('${s3.secretFile}'),
-                ${optionalString (s3.hostname != null) "'hostname' => '${s3.hostname}',"}
-                ${optionalString (s3.port != null) "'port' => ${toString s3.port},"}
-                'use_ssl' => ${boolToString s3.useSsl},
-                ${optionalString (s3.region != null) "'region' => '${s3.region}',"}
-                'use_path_style' => ${boolToString s3.usePathStyle},
-                ${optionalString (s3.sseCKeyFile != null) "'sse_c_key' => nix_read_secret('${s3.sseCKeyFile}'),"}
-              ],
-            ]
-          '';
-
-          showAppStoreSetting = cfg.appstoreEnable != null || cfg.extraApps != {};
-          renderedAppStoreSetting =
-            let
-              x = cfg.appstoreEnable;
-            in
-              if x == null then "false"
-              else boolToString x;
-
-          nextcloudGreaterOrEqualThan = req: versionAtLeast cfg.package.version req;
-
-          overrideConfig = pkgs.writeText "nextcloud-config.php" ''
-            <?php
-            ${optionalString requiresReadSecretFunction ''
-              function nix_read_secret($file) {
-                if (!file_exists($file)) {
-                  throw new \RuntimeException(sprintf(
-                    "Cannot start Nextcloud, secret file %s set by NixOS doesn't seem to "
-                    . "exist! Please make sure that the file exists and has appropriate "
-                    . "permissions for user & group 'nextcloud'!",
-                    $file
-                  ));
-                }
-                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 ],"}
-                [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
-                [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ],
-              ],
-              ${optionalString (showAppStoreSetting) "'appstoreenabled' => ${renderedAppStoreSetting},"}
-              'datadirectory' => '${datadir}/data',
-              'skeletondirectory' => '${cfg.skeletonDirectory}',
-              ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
-              '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}"
-                  ),
-                ''
-              }
-              'dbtype' => '${c.dbtype}',
-              'trusted_domains' => ${writePhpArray ([ cfg.hostName ] ++ c.extraTrustedDomains)},
-              'trusted_proxies' => ${writePhpArray (c.trustedProxies)},
-              ${optionalString (c.defaultPhoneRegion != null) "'default_phone_region' => '${c.defaultPhoneRegion}',"}
-              ${optionalString (nextcloudGreaterOrEqualThan "23") "'profile.enabled' => ${boolToString cfg.globalProfiles},"}
-              ${objectstoreConfig}
-            ];
-
-            $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}";
             dbpass = {
@@ -951,7 +914,6 @@ in {
               # will be omitted.
               ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
               ${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}\"";
               "--admin-user" = ''"${c.adminuser}"'';
@@ -968,7 +930,7 @@ in {
             (i: v: ''
               ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
                 ${toString i} --value="${toString v}"
-            '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains));
+            '') ([ cfg.hostName ] ++ cfg.extraOptions.trusted_domains));
 
         in {
           wantedBy = [ "multi-user.target" ];
@@ -976,6 +938,7 @@ in {
           after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
           requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
           path = [ occ ];
+          restartTriggers = [ overrideConfig ];
           script = ''
             ${optionalString (c.dbpassFile != null) ''
               if [ ! -r "${c.dbpassFile}" ]; then
@@ -996,25 +959,12 @@ in {
               exit 1
             fi
 
-            ln -sf ${cfg.package}/apps ${cfg.home}/
-
-            # Install extra apps
-            ln -sfT \
-              ${pkgs.linkFarm "nix-apps"
-                (mapAttrsToList (name: path: { inherit name path; }) cfg.extraApps)} \
-              ${cfg.home}/nix-apps
-
-            # create nextcloud directories.
-            # if the directories exist already with wrong permissions, we fix that
-            for dir in ${datadir}/config ${datadir}/data ${cfg.home}/store-apps ${cfg.home}/nix-apps; do
-              if [ ! -e $dir ]; then
-                install -o nextcloud -g nextcloud -d $dir
-              elif [ $(stat -c "%G" $dir) != "nextcloud" ]; then
-                chgrp -R nextcloud $dir
+            ${concatMapStrings (name: ''
+              if [ -d "${cfg.home}"/${name} ]; then
+                echo "Cleaning up ${name}; these are now bundled in the webroot store-path!"
+                rm -r "${cfg.home}"/${name}
               fi
-            done
-
-            ln -sf ${overrideConfig} ${datadir}/config/override.config.php
+            '') [ "nix-apps" "apps" ]}
 
             # Do not install if already installed
             if [[ ! -e ${datadir}/config/config.php ]]; then
@@ -1043,7 +993,7 @@ in {
           environment.NEXTCLOUD_CONFIG_DIR = "${datadir}/config";
           serviceConfig.Type = "oneshot";
           serviceConfig.User = "nextcloud";
-          serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${cfg.package}/cron.php";
+          serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${webroot}/cron.php";
         };
         nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
           after = [ "nextcloud-setup.service" ];
@@ -1095,7 +1045,7 @@ in {
         ensureDatabases = [ cfg.config.dbname ];
         ensureUsers = [{
           name = cfg.config.dbuser;
-          ensurePermissions = { "DATABASE ${cfg.config.dbname}" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }];
       };
 
@@ -1104,22 +1054,25 @@ in {
         user = "nextcloud";
       };
 
-      services.nextcloud = lib.mkIf cfg.configureRedis {
-        caching.redis = true;
-        extraOptions = {
+      services.nextcloud = {
+        caching.redis = lib.mkIf cfg.configureRedis true;
+        extraOptions = mkMerge [({
+          datadirectory = lib.mkDefault "${datadir}/data";
+          trusted_domains = [ cfg.hostName ];
+        }) (lib.mkIf cfg.configureRedis {
           "memcache.distributed" = ''\OC\Memcache\Redis'';
           "memcache.locking" = ''\OC\Memcache\Redis'';
           redis = {
             host = config.services.redis.servers.nextcloud.unixSocket;
             port = 0;
           };
-        };
+        })];
       };
 
       services.nginx.enable = mkDefault true;
 
       services.nginx.virtualHosts.${cfg.hostName} = {
-        root = cfg.package;
+        root = webroot;
         locations = {
           "= /robots.txt" = {
             priority = 100;
@@ -1136,18 +1089,6 @@ in {
               }
             '';
           };
-          "/" = {
-            priority = 900;
-            extraConfig = "rewrite ^ /index.php;";
-          };
-          "~ ^/store-apps" = {
-            priority = 201;
-            extraConfig = "root ${cfg.home};";
-          };
-          "~ ^/nix-apps" = {
-            priority = 201;
-            extraConfig = "root ${cfg.home};";
-          };
           "^~ /.well-known" = {
             priority = 210;
             extraConfig = ''
@@ -1164,15 +1105,23 @@ in {
               try_files $uri $uri/ =404;
             '';
           };
-          "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)".extraConfig = ''
-            return 404;
-          '';
-          "~ ^/(?:\\.(?!well-known)|autotest|occ|issue|indie|db_|console)".extraConfig = ''
-            return 404;
-          '';
-          "~ ^\\/(?:index|remote|public|cron|core\\/ajax\\/update|status|ocs\\/v[12]|updater\\/.+|oc[ms]-provider\\/.+|.+\\/richdocumentscode\\/proxy)\\.php(?:$|\\/)" = {
+          "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)" = {
+            priority = 450;
+            extraConfig = ''
+              return 404;
+            '';
+          };
+          "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
+            priority = 450;
+            extraConfig = ''
+              return 404;
+            '';
+          };
+          "~ \\.php(?:$|/)" = {
             priority = 500;
             extraConfig = ''
+              # legacy support (i.e. static files and directories in cfg.package)
+              rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|oc[s${optionalString (!ocmProviderIsNotAStaticDirAnymore) "m"}]-provider\/.+|.+\/richdocumentscode\/proxy) /index.php$request_uri;
               include ${config.services.nginx.package}/conf/fastcgi.conf;
               fastcgi_split_path_info ^(.+?\.php)(\\/.*)$;
               set $path_info $fastcgi_path_info;
@@ -1188,19 +1137,33 @@ in {
               fastcgi_read_timeout ${builtins.toString cfg.fastcgiTimeout}s;
             '';
           };
-          "~ \\.(?:css|js|woff2?|svg|gif|map)$".extraConfig = ''
+          "~ \\.(?:css|js|mjs|svg|gif|png|jpg|jpeg|ico|wasm|tflite|map|html|ttf|bcmap|mp4|webm|ogg|flac)$".extraConfig = ''
             try_files $uri /index.php$request_uri;
             expires 6M;
             access_log off;
+            location ~ \.mjs$ {
+              default_type text/javascript;
+            }
+            location ~ \.wasm$ {
+              default_type application/wasm;
+            }
           '';
-          "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = ''
+          "~ ^\\/(?:updater|ocs-provider${optionalString (!ocmProviderIsNotAStaticDirAnymore) "|ocm-provider"})(?:$|\\/)".extraConfig = ''
             try_files $uri/ =404;
             index index.php;
           '';
-          "~ \\.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$".extraConfig = ''
-            try_files $uri /index.php$request_uri;
-            access_log off;
-          '';
+          "/remote" = {
+            priority = 1500;
+            extraConfig = ''
+              return 301 /remote.php$request_uri;
+            '';
+          };
+          "/" = {
+            priority = 1600;
+            extraConfig = ''
+              try_files $uri $uri/ /index.php$request_uri;
+            '';
+          };
         };
         extraConfig = ''
           index index.php index.html /index.php$request_uri;
diff --git a/nixos/modules/services/web-apps/node-red.nix b/nixos/modules/services/web-apps/node-red.nix
index f4d4ad9681a6b..82f89783d778a 100644
--- a/nixos/modules/services/web-apps/node-red.nix
+++ b/nixos/modules/services/web-apps/node-red.nix
@@ -19,12 +19,7 @@ in
   options.services.node-red = {
     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 = lib.mdDoc "Node-RED package to use.";
-    };
+    package = mkPackageOption pkgs [ "nodePackages" "node-red" ] { };
 
     openFirewall = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
index 3494f2fa21f09..343ca80c9fc27 100644
--- a/nixos/modules/services/web-apps/onlyoffice.nix
+++ b/nixos/modules/services/web-apps/onlyoffice.nix
@@ -26,12 +26,7 @@ in
       '';
     };
 
-    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.";
-    };
+    package = mkPackageOption pkgs "onlyoffice-documentserver" { };
 
     port = mkOption {
       type = types.port;
@@ -198,7 +193,7 @@ in
         ensureDatabases = [ "onlyoffice" ];
         ensureUsers = [{
           name = "onlyoffice";
-          ensurePermissions = { "DATABASE \"onlyoffice\"" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }];
       };
     };
diff --git a/nixos/modules/services/web-apps/openvscode-server.nix b/nixos/modules/services/web-apps/openvscode-server.nix
index 3daf238c57e12..81b9d1f3b4c8c 100644
--- a/nixos/modules/services/web-apps/openvscode-server.nix
+++ b/nixos/modules/services/web-apps/openvscode-server.nix
@@ -10,7 +10,7 @@ in
     services.openvscode-server = {
       enable = lib.mkEnableOption (lib.mdDoc "openvscode-server");
 
-      package = lib.mkPackageOptionMD pkgs "openvscode-server" { };
+      package = lib.mkPackageOption pkgs "openvscode-server" { };
 
       extraPackages = lib.mkOption {
         default = [ ];
@@ -159,6 +159,7 @@ in
     systemd.services.openvscode-server = {
       description = "OpenVSCode server";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       path = cfg.extraPackages;
       environment = cfg.extraEnvironment;
diff --git a/nixos/modules/services/web-apps/openwebrx.nix b/nixos/modules/services/web-apps/openwebrx.nix
index 72c5d6c7818c8..ddc2d66e723cc 100644
--- a/nixos/modules/services/web-apps/openwebrx.nix
+++ b/nixos/modules/services/web-apps/openwebrx.nix
@@ -6,12 +6,7 @@ in
   options.services.openwebrx = with lib; {
     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 = lib.mdDoc "OpenWebRX package to use for the service";
-    };
+    package = mkPackageOption pkgs "openwebrx" { };
   };
 
   config = lib.mkIf cfg.enable {
diff --git a/nixos/modules/services/web-apps/outline.nix b/nixos/modules/services/web-apps/outline.nix
index 1d8298963e6df..702755dfa2ab8 100644
--- a/nixos/modules/services/web-apps/outline.nix
+++ b/nixos/modules/services/web-apps/outline.nix
@@ -117,13 +117,14 @@ in
     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
+        s3-compatible storage can 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.
+        Local filesystem storage can also be used.
 
-        A more detailed guide on setting up S3 is available
-        [here](https://wiki.generaloutline.com/share/125de1cc-9ff6-424b-8415-0d58c809a40f).
+        A more detailed guide on setting up storage is available
+        [here](https://docs.getoutline.com/s/hosting/doc/file-storage-N4M0T6Ypu7).
       '';
       example = lib.literalExpression ''
         {
@@ -136,6 +137,19 @@ in
       '';
       type = lib.types.submodule {
         options = {
+          storageType = lib.mkOption {
+            type = lib.types.enum [ "local" "s3" ];
+            description = lib.mdDoc "File storage type, it can be local or s3.";
+            default = "s3";
+          };
+          localRootDir = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc ''
+              If `storageType` is `local`, this sets the parent directory
+              under which all attachments/images go.
+            '';
+            default = "/var/lib/outline/data";
+          };
           accessKey = lib.mkOption {
             type = lib.types.str;
             description = lib.mdDoc "S3 access key.";
@@ -557,18 +571,52 @@ in
     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} -"
+      (if (cfg.storage.storageType == "s3") then
+        "f ${cfg.storage.secretKeyFile} 0600 ${cfg.user} ${cfg.group} -"
+      else
+        "d ${cfg.storage.localRootDir} 0700 ${cfg.user} ${cfg.group} - -")
     ];
 
     services.postgresql = lib.mkIf (cfg.databaseUrl == "local") {
       enable = true;
       ensureUsers = [{
         name = "outline";
-        ensurePermissions."DATABASE outline" = "ALL PRIVILEGES";
+        ensureDBOwnership = true;
       }];
       ensureDatabases = [ "outline" ];
     };
 
+    # Outline is unable to create the uuid-ossp extension when using postgresql 12, in later version this
+    # extension can be created without superuser permission. This services therefor this extension before
+    # outline starts and postgresql 12 is using on the host.
+    #
+    # Can be removed after postgresql 12 is dropped from nixos.
+    systemd.services.outline-postgresql =
+      let
+        pgsql = config.services.postgresql;
+      in
+        lib.mkIf (cfg.databaseUrl == "local" && pgsql.package == pkgs.postgresql_12) {
+          after = [ "postgresql.service" ];
+          bindsTo = [ "postgresql.service" ];
+          wantedBy = [ "outline.service" ];
+          partOf = [ "outline.service" ];
+          path = [
+            pgsql.package
+          ];
+          script = ''
+            set -o errexit -o pipefail -o nounset -o errtrace
+            shopt -s inherit_errexit
+
+            psql outline -tAc 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'
+          '';
+
+          serviceConfig = {
+            User = pgsql.superUser;
+            Type = "oneshot";
+            RemainAfterExit = true;
+          };
+        };
+
     services.redis.servers.outline = lib.mkIf (cfg.redisUrl == "local") {
       enable = true;
       user = config.services.outline.user;
@@ -599,14 +647,6 @@ in
           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;
@@ -622,8 +662,21 @@ in
           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;
+
+          FILE_STORAGE = cfg.storage.storageType;
+          FILE_STORAGE_UPLOAD_MAX_SIZE = builtins.toString cfg.storage.uploadMaxSize;
+          FILE_STORAGE_LOCAL_ROOT_DIR = cfg.storage.localRootDir;
         }
 
+        (lib.mkIf (cfg.storage.storageType == "s3") {
+          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_FORCE_PATH_STYLE = builtins.toString cfg.storage.forcePathStyle;
+          AWS_S3_ACL = cfg.storage.acl;
+        })
+
         (lib.mkIf (cfg.slackAuthentication != null) {
           SLACK_CLIENT_ID = cfg.slackAuthentication.clientId;
         })
@@ -676,7 +729,9 @@ in
       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.storage.storageType == "s3") ''
+          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})"
         ''}
diff --git a/nixos/modules/services/web-apps/peering-manager.nix b/nixos/modules/services/web-apps/peering-manager.nix
index 641a3644614f7..0382ce7174738 100644
--- a/nixos/modules/services/web-apps/peering-manager.nix
+++ b/nixos/modules/services/web-apps/peering-manager.nix
@@ -2,40 +2,15 @@
 
 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}
-    '';
+  pythonFmt = pkgs.formats.pythonVars {};
+  settingsFile = pythonFmt.generate "peering-manager-settings.py" cfg.settings;
+  extraConfigFile = pkgs.writeTextFile {
+    name = "peering-manager-extraConfig.py";
+    text = cfg.extraConfig;
   };
+  configFile = pkgs.concatText "configuration.py" [ settingsFile extraConfigFile ];
+
   pkg = (pkgs.peering-manager.overrideAttrs (old: {
     postInstall = ''
       ln -s ${configFile} $out/opt/peering-manager/peering_manager/configuration.py
@@ -60,7 +35,15 @@ in {
         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.
+        See this [example](https://github.com/peering-manager/contrib/blob/main/nginx.conf on how to configure this.
+      '';
+    };
+
+    enableScheduledTasks = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Set up [scheduled tasks](https://peering-manager.readthedocs.io/en/stable/setup/8-scheduled-tasks/)
       '';
     };
 
@@ -106,6 +89,30 @@ in {
       '';
     };
 
+    settings = lib.mkOption {
+      description = lib.mdDoc ''
+        Configuration options to set in `configuration.py`.
+        See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
+      '';
+
+      default = { };
+
+      type = lib.types.submodule {
+        freeformType = pythonFmt.type;
+
+        options = {
+          ALLOWED_HOSTS = lib.mkOption {
+            type = with lib.types; listOf str;
+            default = ["*"];
+            description = lib.mdDoc ''
+              A list of valid fully-qualified domain names (FQDNs) and/or IP
+              addresses that can be used to reach the peering manager service.
+            '';
+          };
+        };
+      };
+    };
+
     extraConfig = mkOption {
       type = types.lines;
       default = "";
@@ -135,7 +142,39 @@ in {
   };
 
   config = lib.mkIf cfg.enable {
-    services.peering-manager.plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+    services.peering-manager = {
+      settings = {
+        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;
+          };
+        };
+      };
+
+      extraConfig = ''
+        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()
+      '';
+
+      plugins = lib.mkIf cfg.enableLdap (ps: [ ps.django-auth-ldap ]);
+    };
 
     system.build.peeringManagerPkg = pkg;
 
@@ -147,9 +186,7 @@ in {
       ensureUsers = [
         {
           name = "peering-manager";
-          ensurePermissions = {
-            "DATABASE \"peering-manager\"" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
     };
@@ -159,36 +196,35 @@ in {
     systemd.targets.peering-manager = {
       description = "Target for all Peering Manager services";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.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";
+      defaults = {
+        environment = {
+          PYTHONPATH = pkg.pythonPath;
+        };
+        serviceConfig = {
+          WorkingDirectory = "/var/lib/peering-manager";
+          User = "peering-manager";
+          Group = "peering-manager";
+          StateDirectory = "peering-manager";
+          StateDirectoryMode = "0750";
+          Restart = "on-failure";
+        };
       };
     in {
-      peering-manager-migration = {
+      peering-manager-migration = lib.recursiveUpdate defaults {
         description = "Peering Manager migrations";
         wantedBy = [ "peering-manager.target" ];
-
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
-        };
-
-        serviceConfig = defaultServiceConfig // {
+        serviceConfig = {
           Type = "oneshot";
-          ExecStart = ''
-            ${pkg}/bin/peering-manager migrate
-          '';
+          ExecStart = "${pkg}/bin/peering-manager migrate";
         };
       };
 
-      peering-manager = {
+      peering-manager = lib.recursiveUpdate defaults {
         description = "Peering Manager WSGI Service";
         wantedBy = [ "peering-manager.target" ];
         after = [ "peering-manager-migration.service" ];
@@ -197,11 +233,7 @@ in {
           ${pkg}/bin/peering-manager remove_stale_contenttypes --no-input
         '';
 
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
-        };
-
-        serviceConfig = defaultServiceConfig // {
+        serviceConfig = {
           ExecStart = ''
             ${pkg.python.pkgs.gunicorn}/bin/gunicorn peering_manager.wsgi \
               --bind ${cfg.listenAddress}:${toString cfg.port} \
@@ -210,45 +242,92 @@ in {
         };
       };
 
-      peering-manager-rq = {
+      peering-manager-rq = lib.recursiveUpdate defaults {
         description = "Peering Manager Request Queue Worker";
         wantedBy = [ "peering-manager.target" ];
         after = [ "peering-manager.service" ];
+        serviceConfig.ExecStart = "${pkg}/bin/peering-manager rqworker high default low";
+      };
 
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
+      peering-manager-housekeeping = lib.recursiveUpdate defaults {
+        description = "Peering Manager housekeeping job";
+        after = [ "peering-manager.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkg}/bin/peering-manager housekeeping";
         };
+      };
 
-        serviceConfig = defaultServiceConfig // {
-          ExecStart = ''
-            ${pkg}/bin/peering-manager rqworker high default low
-          '';
+      peering-manager-peeringdb-sync = lib.recursiveUpdate defaults {
+        description = "PeeringDB sync";
+        after = [ "peering-manager.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkg}/bin/peering-manager peeringdb_sync";
         };
       };
 
-      peering-manager-housekeeping = {
-        description = "Peering Manager housekeeping job";
+      peering-manager-prefix-fetch = lib.recursiveUpdate defaults {
+        description = "Fetch IRR AS-SET prefixes";
         after = [ "peering-manager.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkg}/bin/peering-manager grab_prefixes";
+        };
+      };
 
-        environment = {
-          PYTHONPATH = pkg.pythonPath;
+      peering-manager-configuration-deployment = lib.recursiveUpdate defaults {
+        description = "Push configuration to routers";
+        after = [ "peering-manager.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkg}/bin/peering-manager configure_routers";
         };
+      };
 
-        serviceConfig = defaultServiceConfig // {
+      peering-manager-session-poll = lib.recursiveUpdate defaults {
+        description = "Poll peering sessions from routers";
+        after = [ "peering-manager.service" ];
+        serviceConfig = {
           Type = "oneshot";
-          ExecStart = ''
-            ${pkg}/bin/peering-manager housekeeping
-          '';
+          ExecStart = "${pkg}/bin/peering-manager poll_bgp_sessions --all";
         };
       };
     };
 
-    systemd.timers.peering-manager-housekeeping = {
-      description = "Run Peering Manager housekeeping job";
-      wantedBy = [ "timers.target" ];
+    systemd.timers = {
+      peering-manager-housekeeping = {
+        description = "Run Peering Manager housekeeping job";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "daily";
+      };
+
+      peering-manager-peeringdb-sync = {
+        enable = lib.mkDefault cfg.enableScheduledTasks;
+        description = "Sync PeeringDB at 2:30";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "02:30:00";
+      };
+
+      peering-manager-prefix-fetch = {
+        enable = lib.mkDefault cfg.enableScheduledTasks;
+        description = "Fetch IRR AS-SET prefixes at 4:30";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "04:30:00";
+      };
+
+      peering-manager-configuration-deployment = {
+        enable = lib.mkDefault cfg.enableScheduledTasks;
+        description = "Push router configuration every hour 5 minutes before full hour";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "*:55:00";
+      };
 
-      timerConfig = {
-        OnCalendar = "daily";
+      peering-manager-session-poll = {
+        enable = lib.mkDefault cfg.enableScheduledTasks;
+        description = "Poll peering sessions from routers every hour";
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = "*:00:00";
       };
     };
 
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index 17e170c33deea..39c02c81c4231 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -100,19 +100,19 @@ in {
     listenHttp = lib.mkOption {
       type = lib.types.port;
       default = 9000;
-      description = lib.mdDoc "listen port for HTTP server.";
+      description = lib.mdDoc "The port that the local PeerTube web server will listen on.";
     };
 
     listenWeb = lib.mkOption {
       type = lib.types.port;
       default = 9000;
-      description = lib.mdDoc "listen port for WEB server.";
+      description = lib.mdDoc "The public-facing port that PeerTube will be accessible at (likely 80 or 443 if running behind a reverse proxy). Clients will try to access PeerTube at this port.";
     };
 
     enableWebHttps = lib.mkOption {
       type = lib.types.bool;
       default = false;
-      description = lib.mdDoc "Enable or disable HTTPS protocol.";
+      description = lib.mdDoc "Whether clients will access your PeerTube instance with HTTPS. Does NOT configure the PeerTube webserver itself to listen for incoming HTTPS connections.";
     };
 
     dataDirs = lib.mkOption {
@@ -279,7 +279,7 @@ in {
       type = lib.types.package;
       default = pkgs.peertube;
       defaultText = lib.literalExpression "pkgs.peertube";
-      description = lib.mdDoc "Peertube package to use.";
+      description = lib.mdDoc "PeerTube package to use.";
     };
   };
 
@@ -352,6 +352,7 @@ in {
         };
         storage = {
           tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
+          tmp_persistent = lib.mkDefault "/var/lib/peertube/storage/tmp_persistent/";
           bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
           avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
           videos = lib.mkDefault "/var/lib/peertube/storage/videos/";
@@ -521,6 +522,21 @@ in {
           '';
         };
 
+        locations."~ ^/api/v1/runners/jobs/[^/]+/(update|success)$" = {
+          tryFiles = "/dev/null @api";
+          root = cfg.settings.storage.tmp;
+          priority = 1135;
+
+          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;
@@ -607,72 +623,33 @@ in {
           '';
         };
 
-        locations."^~ /lazy-static/avatars/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.avatars;
-          priority = 1330;
-          extraConfig = ''
-            if ($request_method = 'OPTIONS') {
-              ${nginxCommonHeaders}
-              add_header Access-Control-Max-Age         1728000;
-              add_header Cache-Control                  'no-cache';
-              add_header Content-Type                   'text/plain charset=UTF-8';
-              add_header Content-Length                 0;
-              return                                    204;
-            }
-
-            ${nginxCommonHeaders}
-            add_header Cache-Control                    'public, max-age=7200';
-
-            rewrite ^/lazy-static/avatars/(.*)$         /$1 break;
-          '';
-        };
-
-        locations."^~ /lazy-static/banners/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.avatars;
-          priority = 1340;
+        locations."^~ /download/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1410;
           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';
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
 
-            rewrite ^/lazy-static/banners/(.*)$         /$1 break;
+            proxy_limit_rate                            5M;
           '';
         };
 
-        locations."^~ /lazy-static/previews/" = {
-          tryFiles = "$uri @api";
-          root = cfg.settings.storage.previews;
-          priority = 1350;
+        locations."^~ /static/streaming-playlists/private/" = {
+          proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
+          priority = 1420;
           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';
+            proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
+            proxy_set_header Host                       $host;
+            proxy_set_header X-Real-IP                  $remote_addr;
 
-            rewrite ^/lazy-static/previews/(.*)$        /$1 break;
+            proxy_limit_rate                            5M;
           '';
         };
 
-        locations."^~ /static/streaming-playlists/private/" = {
+        locations."^~ /static/web-videos/private/" = {
           proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
-          priority = 1410;
+          priority = 1430;
           extraConfig = ''
             proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
             proxy_set_header Host                       $host;
@@ -684,7 +661,7 @@ in {
 
         locations."^~ /static/webseed/private/" = {
           proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
-          priority = 1420;
+          priority = 1440;
           extraConfig = ''
             proxy_set_header X-Forwarded-For            $proxy_add_x_forwarded_for;
             proxy_set_header Host                       $host;
@@ -694,31 +671,45 @@ in {
           '';
         };
 
-        locations."^~ /static/thumbnails/" = {
+        locations."^~ /static/redundancy/" = {
           tryFiles = "$uri @api";
-          root = cfg.settings.storage.thumbnails;
-          priority = 1430;
+          root = cfg.settings.storage.redundancy;
+          priority = 1450;
           extraConfig = ''
+            set $peertube_limit_rate                    800k;
+
+            if ($request_uri ~ -fragmented.mp4$) {
+              set $peertube_limit_rate                  5M;
+            }
+
             if ($request_method = 'OPTIONS') {
               ${nginxCommonHeaders}
               add_header Access-Control-Max-Age         1728000;
-              add_header Cache-Control                  'no-cache';
               add_header Content-Type                   'text/plain charset=UTF-8';
               add_header Content-Length                 0;
               return                                    204;
             }
+            if ($request_method = 'GET') {
+              ${nginxCommonHeaders}
+
+              access_log                                off;
+            }
 
-            ${nginxCommonHeaders}
-            add_header Cache-Control                    'public, max-age=7200';
+            aio                                         threads;
+            sendfile                                    on;
+            sendfile_max_chunk                          1M;
+
+            limit_rate                                  $peertube_limit_rate;
+            limit_rate_after                            5M;
 
-            rewrite ^/static/thumbnails/(.*)$           /$1 break;
+            rewrite ^/static/redundancy/(.*)$           /$1 break;
           '';
         };
 
-        locations."^~ /static/redundancy/" = {
+        locations."^~ /static/streaming-playlists/" = {
           tryFiles = "$uri @api";
-          root = cfg.settings.storage.redundancy;
-          priority = 1440;
+          root = cfg.settings.storage.streaming_playlists;
+          priority = 1460;
           extraConfig = ''
             set $peertube_limit_rate                    800k;
 
@@ -746,14 +737,14 @@ in {
             limit_rate                                  $peertube_limit_rate;
             limit_rate_after                            5M;
 
-            rewrite ^/static/redundancy/(.*)$           /$1 break;
+            rewrite ^/static/streaming-playlists/(.*)$  /$1 break;
           '';
         };
 
-        locations."^~ /static/streaming-playlists/" = {
+        locations."^~ /static/web-videos/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.streaming_playlists;
-          priority = 1450;
+          priority = 1470;
           extraConfig = ''
             set $peertube_limit_rate                    800k;
 
@@ -788,7 +779,7 @@ in {
         locations."^~ /static/webseed/" = {
           tryFiles = "$uri @api";
           root = cfg.settings.storage.videos;
-          priority = 1460;
+          priority = 1480;
           extraConfig = ''
             set $peertube_limit_rate                    800k;
 
diff --git a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
index dd51bacd75ea1..7a5ab579c408a 100644
--- a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
+++ b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
@@ -20,14 +20,7 @@ in
 
       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 = lib.mdDoc ''
-          Which webgui derivation to use.
-        '';
-      };
+      package = mkPackageOption pkgs "pgpkeyserver-lite" { };
 
       hostname = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/web-apps/photoprism.nix b/nixos/modules/services/web-apps/photoprism.nix
index 423ad5375baab..e25b034844244 100644
--- a/nixos/modules/services/web-apps/photoprism.nix
+++ b/nixos/modules/services/web-apps/photoprism.nix
@@ -77,7 +77,7 @@ in
       '';
     };
 
-    package = lib.mkPackageOptionMD pkgs "photoprism" { };
+    package = lib.mkPackageOption pkgs "photoprism" { };
 
     settings = lib.mkOption {
       type = lib.types.attrsOf lib.types.str;
diff --git a/nixos/modules/services/web-apps/phylactery.nix b/nixos/modules/services/web-apps/phylactery.nix
index 4801bd203b489..488373d0e426c 100644
--- a/nixos/modules/services/web-apps/phylactery.nix
+++ b/nixos/modules/services/web-apps/phylactery.nix
@@ -4,7 +4,7 @@ with lib;
 let cfg = config.services.phylactery;
 in {
   options.services.phylactery = {
-    enable = mkEnableOption (lib.mdDoc "Whether to enable Phylactery server");
+    enable = mkEnableOption (lib.mdDoc "Phylactery server");
 
     host = mkOption {
       type = types.str;
@@ -22,12 +22,7 @@ in {
       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";
-    };
+    package = mkPackageOption pkgs "phylactery" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/web-apps/pict-rs.nix b/nixos/modules/services/web-apps/pict-rs.nix
index e1b8c83335536..983342c37732a 100644
--- a/nixos/modules/services/web-apps/pict-rs.nix
+++ b/nixos/modules/services/web-apps/pict-rs.nix
@@ -14,13 +14,7 @@ in
   options.services.pict-rs = {
     enable = lib.mkEnableOption (lib.mdDoc "pict-rs server");
 
-    package = mkOption {
-      type = types.package;
-      example = lib.literalExpression "pkgs.pict-rs";
-      description = lib.mdDoc ''
-        pict-rs package to use.
-      '';
-    };
+    package = lib.mkPackageOption pkgs "pict-rs" { };
 
     dataDir = mkOption {
       type = types.path;
diff --git a/nixos/modules/services/web-apps/pixelfed.nix b/nixos/modules/services/web-apps/pixelfed.nix
index 159fb52476aab..2add982644478 100644
--- a/nixos/modules/services/web-apps/pixelfed.nix
+++ b/nixos/modules/services/web-apps/pixelfed.nix
@@ -39,8 +39,8 @@ in {
   options.services = {
     pixelfed = {
       enable = mkEnableOption (lib.mdDoc "a Pixelfed instance");
-      package = mkPackageOptionMD pkgs "pixelfed" { };
-      phpPackage = mkPackageOptionMD pkgs "php81" { };
+      package = mkPackageOption pkgs "pixelfed" { };
+      phpPackage = mkPackageOption pkgs "php81" { };
 
       user = mkOption {
         type = types.str;
@@ -271,7 +271,6 @@ in {
         ensureDatabases = [ cfg.database.name ];
         ensureUsers = [{
           name = user;
-          ensurePermissions = { };
         }];
       };
 
diff --git a/nixos/modules/services/web-apps/plantuml-server.nix b/nixos/modules/services/web-apps/plantuml-server.nix
index 5ebee48c3e0b5..b7bdf997d955a 100644
--- a/nixos/modules/services/web-apps/plantuml-server.nix
+++ b/nixos/modules/services/web-apps/plantuml-server.nix
@@ -1,123 +1,110 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    literalExpression
+    mdDoc
+    mkEnableOption
+    mkIf
+    mkOption
+    mkPackageOption
+    mkRemovedOptionModule
+    types
+    ;
 
   cfg = config.services.plantuml-server;
 
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "plantuml-server" "allowPlantumlInclude" ] "This option has been removed from PlantUML.")
+  ];
+
   options = {
     services.plantuml-server = {
-      enable = mkEnableOption (lib.mdDoc "PlantUML server");
+      enable = mkEnableOption (mdDoc "PlantUML server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.plantuml-server;
-        defaultText = literalExpression "pkgs.plantuml-server";
-        description = lib.mdDoc "PlantUML server package to use";
-      };
+      package = mkPackageOption pkgs "plantuml-server" { };
 
       packages = {
-        jdk = mkOption {
-          type = types.package;
-          default = pkgs.jdk;
-          defaultText = literalExpression "pkgs.jdk";
-          description = lib.mdDoc "JDK package to use for the server";
-        };
-        jetty = mkOption {
-          type = types.package;
-          default = pkgs.jetty;
-          defaultText = literalExpression "pkgs.jetty";
-          description = lib.mdDoc "Jetty package to use for the server";
+        jdk = mkPackageOption pkgs "jdk" { };
+        jetty = mkPackageOption pkgs "jetty" {
+          default = [ "jetty_11" ];
+          extraDescription = ''
+            At the time of writing (v1.2023.12), PlantUML Server does not support
+            Jetty versions higher than 12.x.
+
+            Jetty 12.x has introduced major breaking changes, see
+            <https://github.com/jetty/jetty.project/releases/tag/jetty-12.0.0> and
+            <https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-migration-11-to-12>
+          '';
         };
       };
 
       user = mkOption {
         type = types.str;
         default = "plantuml";
-        description = lib.mdDoc "User which runs PlantUML server.";
+        description = mdDoc "User which runs PlantUML server.";
       };
 
       group = mkOption {
         type = types.str;
         default = "plantuml";
-        description = lib.mdDoc "Group which runs PlantUML server.";
+        description = mdDoc "Group which runs PlantUML server.";
       };
 
       home = mkOption {
-        type = types.str;
+        type = types.path;
         default = "/var/lib/plantuml";
-        description = lib.mdDoc "Home directory of the PlantUML server instance.";
+        description = mdDoc "Home directory of the PlantUML server instance.";
       };
 
       listenHost = mkOption {
         type = types.str;
         default = "127.0.0.1";
-        description = lib.mdDoc "Host to listen on.";
+        description = mdDoc "Host to listen on.";
       };
 
       listenPort = mkOption {
         type = types.int;
         default = 8080;
-        description = lib.mdDoc "Port to listen on.";
+        description = mdDoc "Port to listen on.";
       };
 
       plantumlLimitSize = mkOption {
         type = types.int;
         default = 4096;
-        description = lib.mdDoc "Limits image width and height.";
+        description = mdDoc "Limits image width and height.";
       };
 
-      graphvizPackage = mkOption {
-        type = types.package;
-        default = pkgs.graphviz;
-        defaultText = literalExpression "pkgs.graphviz";
-        description = lib.mdDoc "Package containing the dot executable.";
-      };
+      graphvizPackage = mkPackageOption pkgs "graphviz" { };
 
       plantumlStats = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
+        description = mdDoc "Set it to on to enable statistics report (https://plantuml.com/statistics-report).";
       };
 
       httpAuthorization = mkOption {
         type = types.nullOr types.str;
         default = null;
-        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 = lib.mdDoc "Enables !include processing which can read files from the server into diagrams. Files are read relative to the current working directory.";
+        description = mdDoc "When calling the proxy endpoint, the value of HTTP_AUTHORIZATION will be used to set the HTTP Authorization header.";
       };
     };
   };
 
   config = mkIf cfg.enable {
-    users.users.${cfg.user} = {
-      isSystemUser = true;
-      group = cfg.group;
-      home = cfg.home;
-      createHome = true;
-    };
-
-    users.groups.${cfg.group} = {};
-
     systemd.services.plantuml-server = {
       description = "PlantUML server";
       wantedBy = [ "multi-user.target" ];
       path = [ cfg.home ];
+
       environment = {
         PLANTUML_LIMIT_SIZE = builtins.toString cfg.plantumlLimitSize;
         GRAPHVIZ_DOT = "${cfg.graphvizPackage}/bin/dot";
         PLANTUML_STATS = if cfg.plantumlStats then "on" else "off";
         HTTP_AUTHORIZATION = cfg.httpAuthorization;
-        ALLOW_PLANTUML_INCLUDE = if cfg.allowPlantumlInclude then "true" else "false";
       };
       script = ''
       ${cfg.packages.jdk}/bin/java \
@@ -128,13 +115,40 @@ in
           jetty.http.host=${cfg.listenHost} \
           jetty.http.port=${builtins.toString cfg.listenPort}
       '';
+
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
+        StateDirectory = mkIf (cfg.home == "/var/lib/plantuml") "plantuml";
+        StateDirectoryMode = mkIf (cfg.home == "/var/lib/plantuml") "0750";
+
+        # Hardening
+        AmbientCapabilities = [ "" ];
+        CapabilityBoundingSet = [ "" ];
+        DynamicUser = true;
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateNetwork = false;
         PrivateTmp = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectSystem = "strict";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" ];
       };
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ truh ];
+  meta.maintainers = with lib.maintainers; [ truh anthonyroussel ];
 }
diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix
index 911daa53e6587..a6bb81e0b73f7 100644
--- a/nixos/modules/services/web-apps/plausible.nix
+++ b/nixos/modules/services/web-apps/plausible.nix
@@ -9,14 +9,7 @@ in {
   options.services.plausible = {
     enable = mkEnableOption (lib.mdDoc "plausible");
 
-    package = mkPackageOptionMD pkgs "plausible" { };
-
-    releaseCookiePath = mkOption {
-      type = with types; either str path;
-      description = lib.mdDoc ''
-        The path to the file with release cookie. (used for remote connection to the running node).
-      '';
-    };
+    package = mkPackageOption pkgs "plausible" { };
 
     adminUser = {
       name = mkOption {
@@ -78,9 +71,9 @@ in {
     server = {
       disableRegistration = mkOption {
         default = true;
-        type = types.bool;
+        type = types.enum [true false "invite_only"];
         description = lib.mdDoc ''
-          Whether to prohibit creating an account in plausible's UI.
+          Whether to prohibit creating an account in plausible's UI or allow on `invite_only`.
         '';
       };
       secretKeybaseFile = mkOption {
@@ -92,6 +85,13 @@ in {
           framework docs](https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content).
         '';
       };
+      listenAddress = mkOption {
+        default = "127.0.0.1";
+        type = types.str;
+        description = lib.mdDoc ''
+          The IP address on which the server is listening.
+        '';
+      };
       port = mkOption {
         default = 8000;
         type = types.port;
@@ -162,6 +162,10 @@ in {
     };
   };
 
+  imports = [
+    (mkRemovedOptionModule [ "services" "plausible" "releaseCookiePath" ] "Plausible uses no distributed Erlang features, so this option is no longer necessary and was removed")
+  ];
+
   config = mkIf cfg.enable {
     assertions = [
       { assertion = cfg.adminUser.activate -> cfg.database.postgres.setup;
@@ -180,8 +184,6 @@ in {
       enable = true;
     };
 
-    services.epmd.enable = true;
-
     environment.systemPackages = [ cfg.package ];
 
     systemd.services = mkMerge [
@@ -209,7 +211,33 @@ in {
             # Configuration options from
             # https://plausible.io/docs/self-hosting-configuration
             PORT = toString cfg.server.port;
-            DISABLE_REGISTRATION = boolToString cfg.server.disableRegistration;
+            LISTEN_IP = cfg.server.listenAddress;
+
+            # Note [plausible-needs-no-erlang-distributed-features]:
+            # Plausible does not use, and does not plan to use, any of
+            # Erlang's distributed features, see:
+            #     https://github.com/plausible/analytics/pull/1190#issuecomment-1018820934
+            # Thus, disable distribution for improved simplicity and security:
+            #
+            # When distribution is enabled,
+            # Elixir spwans the Erlang VM, which will listen by default on all
+            # interfaces for messages between Erlang nodes (capable of
+            # remote code execution); it can be protected by a cookie; see
+            # https://erlang.org/doc/reference_manual/distributed.html#security).
+            #
+            # It would be possible to restrict the interface to one of our choice
+            # (e.g. localhost or a VPN IP) similar to how we do it with `listenAddress`
+            # for the Plausible web server; if distribution is ever needed in the future,
+            # https://github.com/NixOS/nixpkgs/pull/130297 shows how to do it.
+            #
+            # But since Plausible does not use this feature in any way,
+            # we just disable it.
+            RELEASE_DISTRIBUTION = "none";
+            # Additional safeguard, in case `RELEASE_DISTRIBUTION=none` ever
+            # stops disabling the start of EPMD.
+            ERL_EPMD_ADDRESS = "127.0.0.1";
+
+            DISABLE_REGISTRATION = if isBool cfg.server.disableRegistration then boolToString cfg.server.disableRegistration else cfg.server.disableRegistration;
 
             RELEASE_TMP = "/var/lib/plausible/tmp";
             # Home is needed to connect to the node with iex
@@ -238,7 +266,10 @@ in {
           path = [ cfg.package ]
             ++ optional cfg.database.postgres.setup config.services.postgresql.package;
           script = ''
-            export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )"
+            # Elixir does not start up if `RELEASE_COOKIE` is not set,
+            # even though we set `RELEASE_DISTRIBUTION=none` so the cookie should be unused.
+            # Thus, make a random one, which should then be ignored.
+            export RELEASE_COOKIE=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 20)
             export ADMIN_USER_PWD="$(< $CREDENTIALS_DIRECTORY/ADMIN_USER_PWD )"
             export SECRET_KEY_BASE="$(< $CREDENTIALS_DIRECTORY/SECRET_KEY_BASE )"
 
@@ -248,10 +279,10 @@ in {
             # setup
             ${cfg.package}/createdb.sh
             ${cfg.package}/migrate.sh
+            export IP_GEOLOCATION_DB=${pkgs.dbip-country-lite}/share/dbip/dbip-country-lite.mmdb
+            ${cfg.package}/bin/plausible eval "(Plausible.Release.prepare() ; Plausible.Auth.create_user(\"$ADMIN_USER_NAME\", \"$ADMIN_USER_EMAIL\", \"$ADMIN_USER_PWD\"))"
             ${optionalString cfg.adminUser.activate ''
-              if ! ${cfg.package}/init-admin.sh | grep 'already exists'; then
-                psql -d plausible <<< "UPDATE users SET email_verified=true;"
-              fi
+              psql -d plausible <<< "UPDATE users SET email_verified=true where email = '$ADMIN_USER_EMAIL';"
             ''}
 
             exec plausible start
@@ -265,7 +296,6 @@ in {
             LoadCredential = [
               "ADMIN_USER_PWD:${cfg.adminUser.passwordFile}"
               "SECRET_KEY_BASE:${cfg.server.secretKeybaseFile}"
-              "RELEASE_COOKIE:${cfg.releaseCookiePath}"
             ] ++ lib.optionals (cfg.mail.smtp.passwordFile != null) [ "SMTP_USER_PWD:${cfg.mail.smtp.passwordFile}"];
           };
         };
@@ -296,6 +326,6 @@ in {
     ];
   };
 
-  meta.maintainers = with maintainers; [ ma27 ];
+  meta.maintainers = with maintainers; [ ];
   meta.doc = ./plausible.md;
 }
diff --git a/nixos/modules/services/web-apps/rimgo.nix b/nixos/modules/services/web-apps/rimgo.nix
new file mode 100644
index 0000000000000..4d35473fda31b
--- /dev/null
+++ b/nixos/modules/services/web-apps/rimgo.nix
@@ -0,0 +1,107 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.rimgo;
+  inherit (lib)
+    mkOption
+    mkEnableOption
+    mkPackageOption
+    mkDefault
+    mkIf
+    types
+    literalExpression
+    optionalString
+    getExe
+    mapAttrs
+  ;
+in
+{
+  options.services.rimgo = {
+    enable = mkEnableOption "rimgo";
+    package = mkPackageOption pkgs "rimgo" { };
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = with types; attrsOf str;
+        options = {
+          PORT = mkOption {
+            type = types.port;
+            default = 3000;
+            example = 69420;
+            description = "The port to use.";
+          };
+          ADDRESS = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            example = "1.1.1.1";
+            description = "The address to listen on.";
+          };
+        };
+      };
+      example = literalExpression ''
+        {
+          PORT = 69420;
+          FORCE_WEBP = "1";
+        }
+      '';
+      description = ''
+        Settings for rimgo, see [the official documentation](https://rimgo.codeberg.page/docs/usage/configuration/) for supported options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.rimgo = {
+      description = "Rimgo";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      environment = mapAttrs (_: toString) cfg.settings;
+      serviceConfig = {
+        ExecStart = getExe cfg.package;
+        AmbientCapabilities = mkIf (cfg.settings.PORT < 1024) [
+          "CAP_NET_BIND_SERVICE"
+        ];
+        DynamicUser = true;
+        Restart = "on-failure";
+        RestartSec = "5s";
+        CapabilityBoundingSet = [
+          (optionalString (cfg.settings.PORT < 1024) "CAP_NET_BIND_SERVICE")
+        ];
+        DeviceAllow = [ "" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = cfg.settings.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"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ quantenzitrone ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/sftpgo.nix b/nixos/modules/services/web-apps/sftpgo.nix
index 846478ecbd6d7..1b5111e5a81c3 100644
--- a/nixos/modules/services/web-apps/sftpgo.nix
+++ b/nixos/modules/services/web-apps/sftpgo.nix
@@ -23,14 +23,7 @@ in
       description = mdDoc "sftpgo";
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.sftpgo;
-      defaultText = literalExpression "pkgs.sftpgo";
-      description = mdDoc ''
-        Which SFTPGo package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "sftpgo" { };
 
     extraArgs = mkOption {
       type = with types; listOf str;
diff --git a/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix
index f0505e052e1c7..f9026e04d155f 100644
--- a/nixos/modules/services/web-apps/shiori.nix
+++ b/nixos/modules/services/web-apps/shiori.nix
@@ -8,12 +8,7 @@ in {
     services.shiori = {
       enable = mkEnableOption (lib.mdDoc "Shiori simple bookmarks manager");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.shiori;
-        defaultText = literalExpression "pkgs.shiori";
-        description = lib.mdDoc "The Shiori package to use.";
-      };
+      package = mkPackageOption pkgs "shiori" { };
 
       address = mkOption {
         type = types.str;
@@ -29,6 +24,13 @@ in {
         default = 8080;
         description = lib.mdDoc "The port of the Shiori web application";
       };
+
+      webRoot = mkOption {
+        type = types.str;
+        default = "/";
+        example = "/shiori";
+        description = lib.mdDoc "The root of the Shiori web application";
+      };
     };
   };
 
@@ -40,7 +42,7 @@ in {
       environment.SHIORI_DIR = "/var/lib/shiori";
 
       serviceConfig = {
-        ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}'";
+        ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}' --webroot '${webRoot}'";
 
         DynamicUser = true;
         StateDirectory = "shiori";
diff --git a/nixos/modules/services/web-apps/slskd.nix b/nixos/modules/services/web-apps/slskd.nix
index 33353a59440c3..580f66ec3ac90 100644
--- a/nixos/modules/services/web-apps/slskd.nix
+++ b/nixos/modules/services/web-apps/slskd.nix
@@ -8,7 +8,7 @@ in {
 
     rotateLogs = mkEnableOption "enable an unit and timer that will rotate logs in /var/slskd/logs";
 
-    package = mkPackageOptionMD pkgs "slskd" { };
+    package = mkPackageOption pkgs "slskd" { };
 
     nginx = mkOption {
       description = lib.mdDoc "options for nginx";
diff --git a/nixos/modules/services/web-apps/snipe-it.nix b/nixos/modules/services/web-apps/snipe-it.nix
index e861a41851945..4fbf2bad750bc 100644
--- a/nixos/modules/services/web-apps/snipe-it.nix
+++ b/nixos/modules/services/web-apps/snipe-it.nix
@@ -18,19 +18,23 @@ let
   inherit (snipe-it.passthru) phpPackage;
 
   # shell script for local administration
-  artisan = pkgs.writeScriptBin "snipe-it" ''
+  artisan = (pkgs.writeScriptBin "snipe-it" ''
     #! ${pkgs.runtimeShell}
-    cd ${snipe-it}
+    cd "${snipe-it}/share/php/snipe-it"
     sudo=exec
     if [[ "$USER" != ${user} ]]; then
       sudo='exec /run/wrappers/bin/sudo -u ${user}'
     fi
     $sudo ${phpPackage}/bin/php artisan $*
-  '';
+  '').overrideAttrs (old: {
+    meta = old.meta // {
+      mainProgram = "snipe-it";
+    };
+  });
 in {
   options.services.snipe-it = {
 
-    enable = mkEnableOption (lib.mdDoc "A free open source IT asset/license management system");
+    enable = mkEnableOption (lib.mdDoc "snipe-it, a free open source IT asset/license management system");
 
     user = mkOption {
       default = "snipeit";
@@ -357,7 +361,7 @@ in {
     services.nginx = {
       enable = mkDefault true;
       virtualHosts."${cfg.hostName}" = mkMerge [ cfg.nginx {
-        root = mkForce "${snipe-it}/public";
+        root = mkForce "${snipe-it}/share/php/snipe-it/public";
         extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;";
         locations = {
           "/" = {
@@ -394,7 +398,7 @@ in {
         RuntimeDirectory = "snipe-it/cache";
         RuntimeDirectoryMode = "0700";
       };
-      path = [ pkgs.replace-secret ];
+      path = [ pkgs.replace-secret artisan ];
       script =
         let
           isSecret  = v: isAttrs v && v ? _secret && (isString v._secret || builtins.isPath v._secret);
@@ -451,7 +455,7 @@ in {
           rm "${cfg.dataDir}"/bootstrap/cache/*.php || true
 
           # migrate db
-          ${phpPackage}/bin/php artisan migrate --force
+          ${lib.getExe artisan} migrate --force
 
           # A placeholder file for invalid barcodes
           invalid_barcode_location="${cfg.dataDir}/public/uploads/barcodes/invalid_barcode.gif"
diff --git a/nixos/modules/services/web-apps/suwayomi-server.md b/nixos/modules/services/web-apps/suwayomi-server.md
new file mode 100644
index 0000000000000..ff1e06c8a53ae
--- /dev/null
+++ b/nixos/modules/services/web-apps/suwayomi-server.md
@@ -0,0 +1,108 @@
+# Suwayomi-Server {#module-services-suwayomi-server}
+
+A free and open source manga reader server that runs extensions built for Tachiyomi.
+
+## Basic usage {#module-services-suwayomi-server-basic-usage}
+
+By default, the module will execute Suwayomi-Server backend and web UI:
+
+```nix
+{ ... }:
+
+{
+  services.suwayomi-server = {
+    enable = true;
+  };
+}
+```
+
+It runs in the systemd service named `suwayomi-server` in the data directory `/var/lib/suwayomi-server`.
+
+You can change the default parameters with some other parameters:
+```nix
+{ ... }:
+
+{
+  services.suwayomi-server = {
+    enable = true;
+
+    dataDir = "/var/lib/suwayomi"; # Default is "/var/lib/suwayomi-server"
+    openFirewall = true;
+
+    settings = {
+      server.port = 4567;
+    };
+  };
+}
+```
+
+If you want to create a desktop icon, you can activate the system tray option:
+
+```nix
+{ ... }:
+
+{
+  services.suwayomi-server = {
+    enable = true;
+
+    dataDir = "/var/lib/suwayomi"; # Default is "/var/lib/suwayomi-server"
+    openFirewall = true;
+
+    settings = {
+      server.port = 4567;
+      server.enableSystemTray = true;
+    };
+  };
+}
+```
+
+## Basic authentication {#module-services-suwayomi-server-basic-auth}
+
+You can configure a basic authentication to the web interface with:
+
+```nix
+{ ... }:
+
+{
+  services.suwayomi-server = {
+    enable = true;
+
+    openFirewall = true;
+
+    settings = {
+      server.port = 4567;
+      server = {
+        basicAuthEnabled = true;
+        basicAuthUsername = "username";
+
+        # NOTE: this is not a real upstream option
+        basicAuthPasswordFile = ./path/to/the/password/file;
+      };
+    };
+  };
+}
+```
+
+## Extra configuration {#module-services-suwayomi-server-extra-config}
+
+Not all the configuration options are available directly in this module, but you can add the other options of suwayomi-server with:
+
+```nix
+{ ... }:
+
+{
+  services.suwayomi-server = {
+    enable = true;
+
+    openFirewall = true;
+
+    settings = {
+      server = {
+        port = 4567;
+        autoDownloadNewChapters = false;
+        maxSourcesInParallel" = 6;
+      };
+    };
+  };
+}
+```
diff --git a/nixos/modules/services/web-apps/suwayomi-server.nix b/nixos/modules/services/web-apps/suwayomi-server.nix
new file mode 100644
index 0000000000000..c4c1540edbee5
--- /dev/null
+++ b/nixos/modules/services/web-apps/suwayomi-server.nix
@@ -0,0 +1,260 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.suwayomi-server;
+  inherit (lib) mkOption mdDoc mkEnableOption mkIf types;
+in
+{
+  options = {
+    services.suwayomi-server = {
+      enable = mkEnableOption (mdDoc "Suwayomi, a free and open source manga reader server that runs extensions built for Tachiyomi.");
+
+      package = lib.mkPackageOptionMD pkgs "suwayomi-server" { };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/suwayomi-server";
+        example = "/var/data/mangas";
+        description = mdDoc ''
+          The path to the data directory in which Suwayomi-Server will download scans.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "suwayomi";
+        example = "root";
+        description = mdDoc ''
+          User account under which Suwayomi-Server runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "suwayomi";
+        example = "medias";
+        description = mdDoc ''
+          Group under which Suwayomi-Server runs.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = mdDoc ''
+          Whether to open the firewall for the port in {option}`services.suwayomi-server.settings.server.port`.
+        '';
+      };
+
+      settings = mkOption {
+        type = types.submodule {
+          freeformType =
+            let
+              recursiveAttrsType = with types; attrsOf (nullOr (oneOf [
+                str
+                path
+                int
+                float
+                bool
+                (listOf str)
+                (recursiveAttrsType // { description = "instances of this type recursively"; })
+              ]));
+            in
+            recursiveAttrsType;
+          options = {
+            server = {
+              ip = mkOption {
+                type = types.str;
+                default = "0.0.0.0";
+                example = "127.0.0.1";
+                description = mdDoc ''
+                  The ip that Suwayomi will bind to.
+                '';
+              };
+
+              port = mkOption {
+                type = types.port;
+                default = 8080;
+                example = 4567;
+                description = mdDoc ''
+                  The port that Suwayomi will listen to.
+                '';
+              };
+
+              basicAuthEnabled = mkEnableOption (mdDoc ''
+                Add basic access authentication to Suwayomi-Server.
+                Enabling this option is useful when hosting on a public network/the Internet
+              '');
+
+              basicAuthUsername = mkOption {
+                type = types.nullOr types.str;
+                default = null;
+                description = mdDoc ''
+                  The username value that you have to provide when authenticating.
+                '';
+              };
+
+              # NOTE: this is not a real upstream option
+              basicAuthPasswordFile = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                example = "/var/secrets/suwayomi-server-password";
+                description = mdDoc ''
+                  The password file containing the value that you have to provide when authenticating.
+                '';
+              };
+
+              downloadAsCbz = mkOption {
+                type = types.bool;
+                default = false;
+                description = mdDoc ''
+                  Download chapters as `.cbz` files.
+                '';
+              };
+
+              localSourcePath = mkOption {
+                type = types.path;
+                default = cfg.dataDir;
+                defaultText = lib.literalExpression "suwayomi-server.dataDir";
+                example = "/var/data/local_mangas";
+                description = mdDoc ''
+                  Path to the local source folder.
+                '';
+              };
+
+              systemTrayEnabled = mkOption {
+                type = types.bool;
+                default = false;
+                description = mdDoc ''
+                  Whether to enable a system tray icon, if possible.
+                '';
+              };
+            };
+          };
+        };
+        description = mdDoc ''
+          Configuration to write to {file}`server.conf`.
+          See <https://github.com/Suwayomi/Suwayomi-Server/wiki/Configuring-Suwayomi-Server> for more information.
+        '';
+        default = { };
+        example = {
+          server.socksProxyEnabled = true;
+          server.socksProxyHost = "yourproxyhost.com";
+          server.socksProxyPort = "8080";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [{
+      assertion = with cfg.settings.server; basicAuthEnabled -> (basicAuthUsername != null && basicAuthPasswordFile != null);
+      message = ''
+        [suwayomi-server]: the username and the password file cannot be null when the basic auth is enabled
+      '';
+    }];
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.server.port ];
+
+    users.groups = mkIf (cfg.group == "suwayomi") {
+      suwayomi = { };
+    };
+
+    users.users = mkIf (cfg.user == "suwayomi") {
+      suwayomi = {
+        group = cfg.group;
+        # Need to set the user home because the package writes to ~/.local/Tachidesk
+        home = cfg.dataDir;
+        description = "Suwayomi Daemon user";
+        isSystemUser = true;
+      };
+    };
+
+    systemd.tmpfiles.settings."10-suwayomi-server" = {
+      "${cfg.dataDir}/.local/share/Tachidesk".d = {
+        mode = "0700";
+        inherit (cfg) user group;
+      };
+    };
+
+    systemd.services.suwayomi-server =
+      let
+        flattenConfig = prefix: config:
+          lib.foldl'
+            lib.mergeAttrs
+            { }
+            (lib.attrValues
+              (lib.mapAttrs
+                (k: v:
+                  if !(lib.isAttrs v)
+                  then { "${prefix}${k}" = v; }
+                  else flattenConfig "${prefix}${k}." v
+                )
+                config
+              )
+            );
+
+        #  HOCON is a JSON superset that suwayomi-server use for configuration
+        toHOCON = attr:
+          let
+            attrType = builtins.typeOf attr;
+          in
+          if builtins.elem attrType [ "string" "path" "int" "float" ]
+          then ''"${toString attr}"''
+          else if attrType == "bool"
+          then lib.boolToString attr
+          else if attrType == "list"
+          then "[\n${lib.concatMapStringsSep ",\n" toHOCON attr}\n]"
+          else # attrs, lambda, null
+            throw ''
+              [suwayomi-server]: invalid config value type '${attrType}'.
+            '';
+
+        configFile = pkgs.writeText "server.conf" (lib.pipe cfg.settings [
+          (settings: lib.recursiveUpdate settings {
+            server.basicAuthPasswordFile = null;
+            server.basicAuthPassword =
+              if settings.server.basicAuthEnabled
+              then "$TACHIDESK_SERVER_BASIC_AUTH_PASSWORD"
+              else null;
+          })
+          (flattenConfig "")
+          (lib.filterAttrs (_: x: x != null))
+          (lib.mapAttrsToList (name: value: ''${name} = ${toHOCON value}''))
+          lib.concatLines
+        ]);
+
+      in
+      {
+        description = "A free and open source manga reader server that runs extensions built for Tachiyomi.";
+
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "network-online.target" ];
+        after = [ "network-online.target" ];
+
+        script = ''
+          ${lib.optionalString cfg.settings.server.basicAuthEnabled ''
+            export TACHIDESK_SERVER_BASIC_AUTH_PASSWORD="$(<${cfg.settings.server.basicAuthPasswordFile})"
+          ''}
+          ${lib.getExe pkgs.envsubst} -i ${configFile} -o ${cfg.dataDir}/.local/share/Tachidesk/server.conf
+          ${lib.getExe cfg.package} -Dsuwayomi.tachidesk.config.server.rootDir=${cfg.dataDir}
+        '';
+
+        serviceConfig = {
+          User = cfg.user;
+          Group = cfg.group;
+
+          Type = "simple";
+          Restart = "on-failure";
+
+          StateDirectory = mkIf (cfg.dataDir == "/var/lib/suwayomi-server") "suwayomi-server";
+        };
+      };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ ratcornu ];
+    doc = ./suwayomi-server.md;
+  };
+}
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 3102e6a469536..a8fb37d2c5ecd 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -430,7 +430,7 @@ let
           background processes while not running tt-rss, this method is generally
           viable to keep your feeds up to date.
           Still, there are more robust (and recommended) updating methods
-          available, you can read about them here: http://tt-rss.org/wiki/UpdatingFeeds
+          available, you can read about them here: <https://tt-rss.org/wiki/UpdatingFeeds>
         '';
       };
 
@@ -529,6 +529,15 @@ let
         assertion = cfg.database.password != null -> cfg.database.passwordFile == null;
         message = "Cannot set both password and passwordFile";
       }
+      {
+        assertion = cfg.database.createLocally -> cfg.database.name == cfg.user && cfg.database.user == cfg.user;
+        message = ''
+          When creating a database via NixOS, the db user and db name must be equal!
+          If you already have an existing DB+user and this assertion is new, you can safely set
+          `services.tt-rss.database.createLocally` to `false` because removal of `ensureUsers`
+          and `ensureDatabases` doesn't have any effect.
+        '';
+      }
     ];
 
     services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
@@ -595,47 +604,9 @@ let
       tt-rss = {
         description = "Tiny Tiny RSS feeds update daemon";
 
-        preStart = let
-          callSql = e:
-              if cfg.database.type == "pgsql" then ''
-                  ${optionalString (cfg.database.password != null) "PGPASSWORD=${cfg.database.password}"} \
-                  ${optionalString (cfg.database.passwordFile != null) "PGPASSWORD=$(cat ${cfg.database.passwordFile})"} \
-                  ${config.services.postgresql.package}/bin/psql \
-                    -U ${cfg.database.user} \
-                    ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} --port ${toString dbPort}"} \
-                    -c '${e}' \
-                    ${cfg.database.name}''
-
-              else if cfg.database.type == "mysql" then ''
-                  echo '${e}' | ${config.services.mysql.package}/bin/mysql \
-                    -u ${cfg.database.user} \
-                    ${optionalString (cfg.database.password != null) "-p${cfg.database.password}"} \
-                    ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} -P ${toString dbPort}"} \
-                    ${cfg.database.name}''
-
-              else "";
-
-        in (optionalString (cfg.database.type == "pgsql") ''
-          exists=$(${callSql "select count(*) > 0 from pg_tables where tableowner = user"} \
-          | tail -n+3 | head -n-2 | sed -e 's/[ \n\t]*//')
-
-          if [ "$exists" == 'f' ]; then
-            ${callSql "\\i ${pkgs.tt-rss}/schema/ttrss_schema_${cfg.database.type}.sql"}
-          else
-            echo 'The database contains some data. Leaving it as it is.'
-          fi;
-        '')
-
-        + (optionalString (cfg.database.type == "mysql") ''
-          exists=$(${callSql "select count(*) > 0 from information_schema.tables where table_schema = schema()"} \
-          | tail -n+2 | sed -e 's/[ \n\t]*//')
-
-          if [ "$exists" == '0' ]; then
-            ${callSql "\\. ${pkgs.tt-rss}/schema/ttrss_schema_${cfg.database.type}.sql"}
-          else
-            echo 'The database contains some data. Leaving it as it is.'
-          fi;
-        '');
+        preStart = ''
+          ${pkgs.php81}/bin/php ${cfg.root}/www/update.php --update-schema
+        '';
 
         serviceConfig = {
           User = "${cfg.user}";
@@ -670,8 +641,8 @@ let
       enable = mkDefault true;
       ensureDatabases = [ cfg.database.name ];
       ensureUsers = [
-        { name = cfg.user;
-          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        { name = cfg.database.user;
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix
index 8bc8e8c29259e..b893f2c1f33c7 100644
--- a/nixos/modules/services/web-apps/vikunja.nix
+++ b/nixos/modules/services/web-apps/vikunja.nix
@@ -11,18 +11,8 @@ let
 in {
   options.services.vikunja = with lib; {
     enable = mkEnableOption (lib.mdDoc "vikunja service");
-    package-api = mkOption {
-      default = pkgs.vikunja-api;
-      type = types.package;
-      defaultText = literalExpression "pkgs.vikunja-api";
-      description = lib.mdDoc "vikunja-api derivation to use.";
-    };
-    package-frontend = mkOption {
-      default = pkgs.vikunja-frontend;
-      type = types.package;
-      defaultText = literalExpression "pkgs.vikunja-frontend";
-      description = lib.mdDoc "vikunja-frontend derivation to use.";
-    };
+    package-api = mkPackageOption pkgs "vikunja-api" { };
+    package-frontend = mkPackageOption pkgs "vikunja-frontend" { };
     environmentFiles = mkOption {
       type = types.listOf types.path;
       default = [ ];
@@ -147,5 +137,9 @@ in {
     };
 
     environment.etc."vikunja/config.yaml".source = configFile;
+
+    environment.systemPackages = [
+      cfg.package-api # for admin `vikunja` CLI
+    ];
   };
 }
diff --git a/nixos/modules/services/web-apps/whitebophir.nix b/nixos/modules/services/web-apps/whitebophir.nix
index b673a7c1179e2..dabcf38b2dbdd 100644
--- a/nixos/modules/services/web-apps/whitebophir.nix
+++ b/nixos/modules/services/web-apps/whitebophir.nix
@@ -9,12 +9,7 @@ in {
     services.whitebophir = {
       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 = lib.mdDoc "Whitebophir package to use.";
-      };
+      package = mkPackageOption pkgs "whitebophir" { };
 
       listenAddress = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/web-apps/windmill.nix b/nixos/modules/services/web-apps/windmill.nix
new file mode 100644
index 0000000000000..8e940dabdc1f8
--- /dev/null
+++ b/nixos/modules/services/web-apps/windmill.nix
@@ -0,0 +1,177 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.windmill;
+in
+{
+  options.services.windmill = {
+    enable = lib.mkEnableOption (lib.mdDoc "windmill service");
+
+    serverPort = lib.mkOption {
+      type = lib.types.port;
+      default = 8001;
+      description = lib.mdDoc "Port the windmill server listens on.";
+    };
+
+    lspPort = lib.mkOption {
+      type = lib.types.port;
+      default = 3001;
+      description = lib.mdDoc "Port the windmill lsp listens on.";
+    };
+
+    database = {
+      name = lib.mkOption {
+        type = lib.types.str;
+        # the simplest database setup is to have the database named like the user.
+        default = "windmill";
+        description = lib.mdDoc "Database name.";
+      };
+
+      user = lib.mkOption {
+        type = lib.types.str;
+        # the simplest database setup is to have the database user like the name.
+        default = "windmill";
+        description = lib.mdDoc "Database user.";
+      };
+
+      urlPath = lib.mkOption {
+        type = lib.types.path;
+        description = lib.mdDoc ''
+          Path to the file containing the database url windmill should connect to. This is not deducted from database user and name as it might contain a secret
+        '';
+        example = "config.age.secrets.DATABASE_URL_FILE.path";
+      };
+      createLocally = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = lib.mdDoc "Whether to create a local database automatically.";
+      };
+    };
+
+    baseUrl = lib.mkOption {
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        The base url that windmill will be served on.
+      '';
+      example = "https://windmill.example.com";
+    };
+
+    logLevel = lib.mkOption {
+      type = lib.types.enum [ "error" "warn" "info" "debug" "trace" ];
+      default = "info";
+      description = lib.mdDoc "Log level";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    services.postgresql = lib.optionalAttrs (cfg.database.createLocally) {
+      enable = lib.mkDefault true;
+
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensureDBOwnership = true;
+        }
+      ];
+
+   };
+
+   systemd.services =
+    let
+      serviceConfig = {
+        DynamicUser = true;
+        # using the same user to simplify db connection
+        User = cfg.database.user;
+        ExecStart = "${pkgs.windmill}/bin/windmill";
+
+        Restart = "always";
+        LoadCredential = [
+          "DATABASE_URL_FILE:${cfg.database.urlPath}"
+        ];
+      };
+    in
+    {
+
+    # coming from https://github.com/windmill-labs/windmill/blob/main/init-db-as-superuser.sql
+    # modified to not grant priviledges on all tables
+    # create role windmill_user and windmill_admin only if they don't exist
+    postgresql.postStart = lib.mkIf cfg.database.createLocally (lib.mkAfter ''
+      $PSQL -tA <<"EOF"
+DO $$
+BEGIN
+    IF NOT EXISTS (
+        SELECT FROM pg_catalog.pg_roles
+        WHERE rolname = 'windmill_user'
+    ) THEN
+        CREATE ROLE windmill_user;
+        GRANT ALL PRIVILEGES ON DATABASE ${cfg.database.name} TO windmill_user;
+    ELSE
+      RAISE NOTICE 'Role "windmill_user" already exists. Skipping.';
+    END IF;
+    IF NOT EXISTS (
+        SELECT FROM pg_catalog.pg_roles
+        WHERE rolname = 'windmill_admin'
+    ) THEN
+      CREATE ROLE windmill_admin WITH BYPASSRLS;
+      GRANT windmill_user TO windmill_admin;
+    ELSE
+      RAISE NOTICE 'Role "windmill_admin" already exists. Skipping.';
+    END IF;
+    GRANT windmill_admin TO windmill;
+END
+$$;
+EOF
+    '');
+
+     windmill-server = {
+        description = "Windmill server";
+        after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = serviceConfig // { StateDirectory = "windmill";};
+
+        environment = {
+          DATABASE_URL_FILE = "%d/DATABASE_URL_FILE";
+          PORT = builtins.toString cfg.serverPort;
+          WM_BASE_URL = cfg.baseUrl;
+          RUST_LOG = cfg.logLevel;
+          MODE = "server";
+        };
+      };
+
+     windmill-worker = {
+        description = "Windmill worker";
+        after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = serviceConfig // { StateDirectory = "windmill-worker";};
+
+        environment = {
+          DATABASE_URL_FILE = "%d/DATABASE_URL_FILE";
+          WM_BASE_URL = cfg.baseUrl;
+          RUST_LOG = cfg.logLevel;
+          MODE = "worker";
+          WORKER_GROUP = "default";
+          KEEP_JOB_DIR = "false";
+        };
+      };
+
+     windmill-worker-native = {
+        description = "Windmill worker native";
+        after = [ "network.target" ] ++ lib.optional cfg.database.createLocally "postgresql.service";
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = serviceConfig // { StateDirectory = "windmill-worker-native";};
+
+        environment = {
+          DATABASE_URL_FILE = "%d/DATABASE_URL_FILE";
+          WM_BASE_URL = cfg.baseUrl;
+          RUST_LOG = cfg.logLevel;
+          MODE = "worker";
+          WORKER_GROUP = "native";
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index d4c987da1144c..2f7306309d694 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -104,12 +104,7 @@ let
   siteOpts = { lib, name, config, ... }:
     {
       options = {
-        package = mkOption {
-          type = types.package;
-          default = pkgs.wordpress;
-          defaultText = literalExpression "pkgs.wordpress";
-          description = lib.mdDoc "Which WordPress package to use.";
-        };
+        package = mkPackageOption pkgs "wordpress" { };
 
         uploadsDir = mkOption {
           type = types.path;
@@ -179,22 +174,22 @@ let
             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 {
+              (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"
+                  name = "wordpress-''${pkgs.wordpress.version}-language-de";
                   sha256 = "sha256-dlas0rXTSV4JAl8f/UyMbig57yURRYRhTMtJwF9g8h0=";
                 };
                 installPhase = "mkdir -p $out; cp -r ./wp-content/languages/* $out/";
-              };
-            )];
+              })
+            ];
           '';
         };
 
diff --git a/nixos/modules/services/web-apps/writefreely.nix b/nixos/modules/services/web-apps/writefreely.nix
index a7671aa717f43..f92afa9276e3c 100644
--- a/nixos/modules/services/web-apps/writefreely.nix
+++ b/nixos/modules/services/web-apps/writefreely.nix
@@ -120,7 +120,7 @@ let
     withConfigFile ''
       query () {
         local result=$(${sqlite}/bin/sqlite3 \
-          '${cfg.stateDir}/${settings.database.filename}'
+          '${cfg.stateDir}/${settings.database.filename}' \
           "$1" \
         )
 
diff --git a/nixos/modules/services/web-apps/youtrack.nix b/nixos/modules/services/web-apps/youtrack.nix
index 09a2b9e965c0b..79e1d12e0abba 100644
--- a/nixos/modules/services/web-apps/youtrack.nix
+++ b/nixos/modules/services/web-apps/youtrack.nix
@@ -54,14 +54,7 @@ in
       type = types.attrsOf types.str;
     };
 
-    package = mkOption {
-      description = lib.mdDoc ''
-        Package to use.
-      '';
-      type = types.package;
-      default = pkgs.youtrack;
-      defaultText = literalExpression "pkgs.youtrack";
-    };
+    package = mkPackageOption pkgs "youtrack" { };
 
     port = mkOption {
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
index 2cea7e7cea723..4f6d7e4e6c1ca 100644
--- a/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -2,7 +2,7 @@
 
 let
 
-  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption types;
   inherit (lib) literalExpression mapAttrs optionalString versionAtLeast;
 
   cfg = config.services.zabbixWeb;
@@ -42,12 +42,7 @@ in
     zabbixWeb = {
       enable = mkEnableOption (lib.mdDoc "the Zabbix web interface");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.zabbix.web;
-        defaultText = literalExpression "zabbix.web";
-        description = lib.mdDoc "Which Zabbix package to use.";
-      };
+      package = mkPackageOption pkgs [ "zabbix" "web" ] { };
 
       server = {
         port = mkOption {
diff --git a/nixos/modules/services/web-apps/zitadel.nix b/nixos/modules/services/web-apps/zitadel.nix
new file mode 100644
index 0000000000000..99b0a0bc56f67
--- /dev/null
+++ b/nixos/modules/services/web-apps/zitadel.nix
@@ -0,0 +1,223 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.zitadel;
+
+  settingsFormat = pkgs.formats.yaml { };
+in
+{
+  options.services.zitadel =
+    let inherit (lib) mkEnableOption mkOption mkPackageOption types;
+    in {
+      enable = mkEnableOption "ZITADEL, a user and identity access management platform";
+
+      package = mkPackageOption pkgs "ZITADEL" { default = [ "zitadel" ]; };
+
+      user = mkOption {
+        type = types.str;
+        default = "zitadel";
+        description = "The user to run ZITADEL under.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "zitadel";
+        description = "The group to run ZITADEL under.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open the port specified in `listenPort` in the firewall.
+        '';
+      };
+
+      masterKeyFile = mkOption {
+        type = types.path;
+        description = ''
+          Path to a file containing a master encryption key for ZITADEL. The
+          key must be 32 bytes.
+        '';
+      };
+
+      tlsMode = mkOption {
+        type = types.enum [ "external" "enabled" "disabled" ];
+        default = "external";
+        example = "enabled";
+        description = ''
+          The TLS mode to use. Options are:
+
+          - enabled: ZITADEL accepts HTTPS connections directly. You must
+            configure TLS if this option is selected.
+          - external: ZITADEL forces HTTPS connections, with TLS terminated at a
+            reverse proxy.
+          - disabled: ZITADEL accepts HTTP connections only. Should only be used
+            for testing.
+        '';
+      };
+
+      settings = mkOption {
+        type = lib.types.submodule {
+          freeformType = settingsFormat.type;
+
+          options = {
+            Port = mkOption {
+              type = types.port;
+              default = 8080;
+              description = "The port that ZITADEL listens on.";
+            };
+
+            TLS = {
+              KeyPath = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                description = "Path to the TLS certificate private key.";
+              };
+              Key = mkOption {
+                type = types.nullOr types.str;
+                default = null;
+                description = ''
+                  The TLS certificate private key, as a base64-encoded string.
+
+                  Note that the contents of this option will be added to the Nix
+                  store as world-readable plain text. Set
+                  [KeyPath](#opt-services.zitadel.settings.TLS.KeyPath) instead
+                  if this is undesired.
+                '';
+              };
+              CertPath = mkOption {
+                type = types.nullOr types.path;
+                default = null;
+                description = "Path to the TLS certificate.";
+              };
+              Cert = mkOption {
+                type = types.nullOr types.str;
+                default = null;
+                description = ''
+                  The TLS certificate, as a base64-encoded string.
+
+                  Note that the contents of this option will be added to the Nix
+                  store as world-readable plain text. Set
+                  [CertPath](#opt-services.zitadel.settings.TLS.CertPath) instead
+                  if this is undesired.
+                '';
+              };
+            };
+          };
+        };
+        default = { };
+        example = lib.literalExpression ''
+          {
+            Port = 8123;
+            ExternalDomain = "example.com";
+            TLS = {
+              CertPath = "/path/to/cert.pem";
+              KeyPath = "/path/to/cert.key";
+            };
+            Database.cockroach.Host = "db.example.com";
+          };
+        '';
+        description = ''
+          Contents of the runtime configuration file. See
+          https://zitadel.com/docs/self-hosting/manage/configure for more
+          details.
+        '';
+      };
+
+      extraSettingsPaths = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        description = ''
+          A list of paths to extra settings files. These will override the
+          values set in [settings](#opt-services.zitadel.settings). Useful if
+          you want to keep sensitive secrets out of the Nix store.
+        '';
+      };
+
+      steps = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        example = lib.literalExpression ''
+          {
+            FirstInstance = {
+              InstanceName = "Example";
+              Org.Human = {
+                UserName = "foobar";
+                FirstName = "Foo";
+                LastName = "Bar";
+              };
+            };
+          }
+        '';
+        description = ''
+          Contents of the database initialization config file. See
+          https://zitadel.com/docs/self-hosting/manage/configure for more
+          details.
+        '';
+      };
+
+      extraStepsPaths = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        description = ''
+          A list of paths to extra steps files. These will override the values
+          set in [steps](#opt-services.zitadel.steps). Useful if you want to
+          keep sensitive secrets out of the Nix store.
+        '';
+      };
+    };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [{
+      assertion = cfg.tlsMode == "enabled"
+        -> ((cfg.settings.TLS.Key != null || cfg.settings.TLS.KeyPath != null)
+        && (cfg.settings.TLS.Cert != null || cfg.settings.TLS.CertPath
+        != null));
+      message = ''
+        A TLS certificate and key must be configured in
+        services.zitadel.settings.TLS if services.zitadel.tlsMode is enabled.
+      '';
+    }];
+
+    networking.firewall.allowedTCPPorts =
+      lib.mkIf cfg.openFirewall [ cfg.settings.Port ];
+
+    systemd.services.zitadel =
+      let
+        configFile = settingsFormat.generate "config.yaml" cfg.settings;
+        stepsFile = settingsFormat.generate "steps.yaml" cfg.steps;
+
+        args = lib.cli.toGNUCommandLineShell { } {
+          config = cfg.extraSettingsPaths ++ [ configFile ];
+          steps = cfg.extraStepsPaths ++ [ stepsFile ];
+          masterkeyFile = cfg.masterKeyFile;
+          inherit (cfg) tlsMode;
+        };
+      in
+      {
+        description = "ZITADEL identity access management";
+        path = [ cfg.package ];
+        wantedBy = [ "multi-user.target" ];
+
+        script = ''
+          zitadel start-from-init ${args}
+        '';
+
+        serviceConfig = {
+          Type = "simple";
+          User = cfg.user;
+          Group = cfg.group;
+          Restart = "on-failure";
+        };
+      };
+
+    users.users.zitadel = lib.mkIf (cfg.user == "zitadel") {
+      isSystemUser = true;
+      group = cfg.group;
+    };
+    users.groups.zitadel = lib.mkIf (cfg.group == "zitadel") { };
+  };
+
+  meta.maintainers = with lib.maintainers; [ Sorixelle ];
+}
diff --git a/nixos/modules/services/web-servers/agate.nix b/nixos/modules/services/web-servers/agate.nix
index a0c8a8c94ee5a..e03174c87945b 100644
--- a/nixos/modules/services/web-servers/agate.nix
+++ b/nixos/modules/services/web-servers/agate.nix
@@ -10,12 +10,7 @@ in
     services.agate = {
       enable = mkEnableOption (lib.mdDoc "Agate Server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.agate;
-        defaultText = literalExpression "pkgs.agate";
-        description = lib.mdDoc "The package to use";
-      };
+      package = mkPackageOption pkgs "agate" { };
 
       addresses = mkOption {
         type = types.listOf types.str;
@@ -76,6 +71,7 @@ in
     systemd.services.agate = {
       description = "Agate";
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = [ "network.target" "network-online.target" ];
 
       script =
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 588f5ee4d003a..016e4885a095a 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -406,14 +406,7 @@ in
 
       enable = mkEnableOption (lib.mdDoc "the Apache HTTP Server");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.apacheHttpd;
-        defaultText = literalExpression "pkgs.apacheHttpd";
-        description = lib.mdDoc ''
-          Overridable attribute of the Apache HTTP Server package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "apacheHttpd" { };
 
       configFile = mkOption {
         type = types.path;
@@ -557,14 +550,7 @@ in
         description = lib.mdDoc "Whether to enable the PHP module.";
       };
 
-      phpPackage = mkOption {
-        type = types.package;
-        default = pkgs.php;
-        defaultText = literalExpression "pkgs.php";
-        description = lib.mdDoc ''
-          Overridable attribute of the PHP package to use.
-        '';
-      };
+      phpPackage = mkPackageOption pkgs "php" { };
 
       enablePerl = mkOption {
         type = types.bool;
diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix
index cec0b379f67ae..95dc219d108cc 100644
--- a/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixos/modules/services/web-servers/caddy/default.nix
@@ -36,6 +36,7 @@ let
             ${cfg.globalConfig}
           }
           ${cfg.extraConfig}
+          ${concatMapStringsSep "\n" mkVHostConf virtualHosts}
         '';
 
         Caddyfile-formatted = pkgs.runCommand "Caddyfile-formatted" { nativeBuildInputs = [ cfg.package ]; } ''
@@ -93,14 +94,7 @@ in
       '';
     };
 
-    package = mkOption {
-      default = pkgs.caddy;
-      defaultText = literalExpression "pkgs.caddy";
-      type = types.package;
-      description = lib.mdDoc ''
-        Caddy package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "caddy" { };
 
     dataDir = mkOption {
       type = types.path;
@@ -153,7 +147,7 @@ in
       default = configFile;
       defaultText = "A Caddyfile automatically generated by values from services.caddy.*";
       example = literalExpression ''
-        pkgs.writeTextDir "Caddyfile" '''
+        pkgs.writeText "Caddyfile" '''
           example.com
 
           root * /var/www/wordpress
@@ -170,9 +164,9 @@ in
     };
 
     adapter = mkOption {
-      default = if (builtins.baseNameOf cfg.configFile) == "Caddyfile" then "caddyfile" else null;
+      default = if ((cfg.configFile != configFile) || (builtins.baseNameOf cfg.configFile) == "Caddyfile") then "caddyfile" else null;
       defaultText = literalExpression ''
-        if (builtins.baseNameOf cfg.configFile) == "Caddyfile" then "caddyfile" else null
+        if ((cfg.configFile != configFile) || (builtins.baseNameOf cfg.configFile) == "Caddyfile") then "caddyfile" else null
       '';
       example = literalExpression "nginx";
       type = with types; nullOr str;
@@ -340,7 +334,6 @@ in
       groups = config.users.groups;
     }) acmeHosts;
 
-    services.caddy.extraConfig = concatMapStringsSep "\n" mkVHostConf virtualHosts;
     services.caddy.globalConfig = ''
       ${optionalString (cfg.email != null) "email ${cfg.email}"}
       ${optionalString (cfg.acmeCA != null) "acme_ca ${cfg.acmeCA}"}
@@ -349,8 +342,9 @@ in
       }
     '';
 
-    # https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size
+    # https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes
     boot.kernel.sysctl."net.core.rmem_max" = mkDefault 2500000;
+    boot.kernel.sysctl."net.core.wmem_max" = mkDefault 2500000;
 
     systemd.packages = [ cfg.package ];
     systemd.services.caddy = {
@@ -376,7 +370,9 @@ in
         ReadWriteDirectories = cfg.dataDir;
         StateDirectory = mkIf (cfg.dataDir == "/var/lib/caddy") [ "caddy" ];
         LogsDirectory = mkIf (cfg.logDir == "/var/log/caddy") [ "caddy" ];
-        Restart = "on-abnormal";
+        Restart = "on-failure";
+        RestartPreventExitStatus = 1;
+        RestartSec = "5s";
 
         # TODO: attempt to upstream these options
         NoNewPrivileges = true;
diff --git a/nixos/modules/services/web-servers/garage.nix b/nixos/modules/services/web-servers/garage.nix
index 8b5734b5a2ced..47b4c6ab416e8 100644
--- a/nixos/modules/services/web-servers/garage.nix
+++ b/nixos/modules/services/web-servers/garage.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.services.garage;
-  toml = pkgs.formats.toml {};
+  toml = pkgs.formats.toml { };
   configFile = toml.generate "garage.toml" cfg.settings;
 in
 {
@@ -19,12 +19,18 @@ in
     extraEnvironment = mkOption {
       type = types.attrsOf types.str;
       description = lib.mdDoc "Extra environment variables to pass to the Garage server.";
-      default = {};
-      example = { RUST_BACKTRACE="yes"; };
+      default = { };
+      example = { RUST_BACKTRACE = "yes"; };
+    };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      description = lib.mdDoc "File containing environment variables to be passed to the Garage server.";
+      default = null;
     };
 
     logLevel = mkOption {
-      type = types.enum (["info" "debug" "trace"]);
+      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.";
@@ -59,12 +65,8 @@ in
     };
 
     package = mkOption {
-      # TODO: when 23.05 is released and if Garage 0.9 is the default, put a stateVersion check.
-      default = if versionAtLeast config.system.stateVersion "23.05" then pkgs.garage_0_8
-                else pkgs.garage_0_7;
-      defaultText = literalExpression "pkgs.garage_0_7";
       type = types.package;
-      description = lib.mdDoc "Garage package to use, if you are upgrading from a major version, please read NixOS and Garage release notes for upgrade instructions.";
+      description = lib.mdDoc "Garage package to use, needs to be set explicitly. If you are upgrading from a major version, please read NixOS and Garage release notes for upgrade instructions.";
     };
   };
 
@@ -80,14 +82,15 @@ in
       after = [ "network.target" "network-online.target" ];
       wants = [ "network.target" "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
-      restartTriggers = [ configFile ];
+      restartTriggers = [ configFile ] ++ (lib.optional (cfg.environmentFile != null) cfg.environmentFile);
       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";
+        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;
+        EnvironmentFile = lib.optional (cfg.environmentFile != null) cfg.environmentFile;
       };
       environment = {
         RUST_LOG = lib.mkDefault "garage=${cfg.logLevel}";
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
index 4434965b217a0..9d30fdc0caab0 100644
--- a/nixos/modules/services/web-servers/hydron.nix
+++ b/nixos/modules/services/web-servers/hydron.nix
@@ -93,7 +93,7 @@ in with lib; {
       ensureDatabases = [ "hydron" ];
       ensureUsers = [
         { name = "hydron";
-          ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/modules/services/web-servers/jboss/builder.sh b/nixos/modules/services/web-servers/jboss/builder.sh
index ac573089cd5a7..8c49b87db0604 100644
--- a/nixos/modules/services/web-servers/jboss/builder.sh
+++ b/nixos/modules/services/web-servers/jboss/builder.sh
@@ -1,6 +1,6 @@
 set -e
 
-if [ -e .attrs.sh ]; then source .attrs.sh; fi
+if [ -e "$NIX_ATTRS_SH_FILE" ]; then . "$NIX_ATTRS_SH_FILE"; elif [ -f .attrs.sh ]; then . .attrs.sh; fi
 source $stdenv/setup
 
 mkdir -p $out/bin
diff --git a/nixos/modules/services/web-servers/keter/default.nix b/nixos/modules/services/web-servers/keter/default.nix
index 3916c486475de..0cd9c30cea14d 100644
--- a/nixos/modules/services/web-servers/keter/default.nix
+++ b/nixos/modules/services/web-servers/keter/default.nix
@@ -16,7 +16,7 @@ in
   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.
+Keep an old app running and swap the ports when the new one is booted
 '');
 
     root = lib.mkOption {
diff --git a/nixos/modules/services/web-servers/lighttpd/default.nix b/nixos/modules/services/web-servers/lighttpd/default.nix
index 0438e12e7da82..3a33137b27d20 100644
--- a/nixos/modules/services/web-servers/lighttpd/default.nix
+++ b/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -10,7 +10,7 @@ let
 
   # List of known lighttpd modules, ordered by how the lighttpd documentation
   # recommends them being imported:
-  # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+  # https://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
   #
   # Some modules are always imported and should not appear in the config:
   # disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ];
@@ -84,8 +84,8 @@ let
       # server.modules += () entries in each sub-service extraConfig snippet,
       # read this:
       #
-      #   http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
-      #   http://redmine.lighttpd.net/issues/2337
+      #   https://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+      #   https://redmine.lighttpd.net/issues/2337
       #
       # Basically, lighttpd doesn't want to load (or even silently ignore) a
       # module for a second time, and there is no way to check if a module has
@@ -135,14 +135,7 @@ in
         '';
       };
 
-      package = mkOption {
-        default = pkgs.lighttpd;
-        defaultText = lib.literalExpression "pkgs.lighttpd";
-        type = types.package;
-        description = lib.mdDoc ''
-          lighttpd package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "lighttpd" { };
 
       port = mkOption {
         default = 80;
@@ -253,6 +246,7 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig.ExecStart = "${cfg.package}/sbin/lighttpd -D -f ${configFile}";
+      serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR1 $MAINPID";
       # SIGINT => graceful shutdown
       serviceConfig.KillSignal = "SIGINT";
     };
diff --git a/nixos/modules/services/web-servers/mighttpd2.nix b/nixos/modules/services/web-servers/mighttpd2.nix
index 2d887af87c79b..bb75dc4f2ff47 100644
--- a/nixos/modules/services/web-servers/mighttpd2.nix
+++ b/nixos/modules/services/web-servers/mighttpd2.nix
@@ -44,7 +44,7 @@ in {
       type = types.lines;
       description = lib.mdDoc ''
         Verbatim config file to use
-        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+        (see https://kazu-yamamoto.github.io/mighttpd2/config.html)
       '';
     };
 
@@ -78,7 +78,7 @@ in {
       type = types.lines;
       description = lib.mdDoc ''
         Verbatim routing file to use
-        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+        (see https://kazu-yamamoto.github.io/mighttpd2/config.html)
       '';
     };
 
@@ -101,6 +101,7 @@ in {
       ];
     systemd.services.mighttpd2 = {
       description = "Mighttpd2 web server";
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
diff --git a/nixos/modules/services/web-servers/minio.nix b/nixos/modules/services/web-servers/minio.nix
index 0bc7421a0e32c..be6946657e23d 100644
--- a/nixos/modules/services/web-servers/minio.nix
+++ b/nixos/modules/services/web-servers/minio.nix
@@ -85,12 +85,7 @@ in
       description = lib.mdDoc "Enable or disable access to web UI.";
     };
 
-    package = mkOption {
-      default = pkgs.minio;
-      defaultText = literalExpression "pkgs.minio";
-      type = types.package;
-      description = lib.mdDoc "Minio package to use.";
-    };
+    package = mkPackageOption pkgs "minio" { };
   };
 
   config = mkIf cfg.enable {
@@ -103,6 +98,7 @@ in
 
       services.minio = {
         description = "Minio Object Storage";
+        wants = [ "network-online.target" ];
         after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index fccc31b5116cb..6799de6c7d96c 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -35,6 +35,7 @@ let
   compressMimeTypes = [
     "application/atom+xml"
     "application/geo+json"
+    "application/javascript" # Deprecated by IETF RFC 9239, but still widely used
     "application/json"
     "application/ld+json"
     "application/manifest+json"
@@ -146,6 +147,10 @@ let
     error_log ${cfg.logError};
     daemon off;
 
+    ${optionalString cfg.enableQuicBPF ''
+      quic_bpf on;
+    ''}
+
     ${cfg.config}
 
     ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
@@ -325,16 +330,16 @@ let
         listenString = { addr, port, ssl, proxyProtocol ? false, extraParameters ? [], ... }:
           # UDP listener for QUIC transport protocol.
           (optionalString (ssl && vhost.quic) ("
-            listen ${addr}:${toString port} quic "
+            listen ${addr}${optionalString (port != null) ":${toString port}"} quic "
           + optionalString vhost.default "default_server "
           + optionalString vhost.reuseport "reuseport "
           + optionalString (extraParameters != []) (concatStringsSep " "
-            (let inCompatibleParameters = [ "ssl" "proxy_protocol" "http2" ];
-                isCompatibleParameter = param: !(any (p: p == param) inCompatibleParameters);
+            (let inCompatibleParameters = [ "accept_filter" "backlog" "deferred" "fastopen" "http2" "proxy_protocol" "so_keepalive" "ssl" ];
+                isCompatibleParameter = param: !(any (p: lib.hasPrefix p param) inCompatibleParameters);
             in filter isCompatibleParameter extraParameters))
           + ";"))
           + "
-            listen ${addr}:${toString port} "
+            listen ${addr}${optionalString (port != null) ":${toString port}"} "
           + optionalString (ssl && vhost.http2 && oldHTTP2) "http2 "
           + optionalString ssl "ssl "
           + optionalString vhost.default "default_server "
@@ -347,10 +352,11 @@ let
 
         # The acme-challenge location doesn't need to be added if we are not using any automated
         # certificate provisioning and can also be omitted when we use a certificate obtained via a DNS-01 challenge
-        acmeLocation = optionalString (vhost.enableACME || (vhost.useACMEHost != null && config.security.acme.certs.${vhost.useACMEHost}.dnsProvider == null)) ''
+        acmeLocation = optionalString (vhost.enableACME || (vhost.useACMEHost != null && config.security.acme.certs.${vhost.useACMEHost}.dnsProvider == null))
           # 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};"}
@@ -370,10 +376,11 @@ let
             ${concatMapStringsSep "\n" listenString redirectListen}
 
             server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
-            ${acmeLocation}
+
             location / {
-              return 301 https://$host$request_uri;
+              return ${toString vhost.redirectCode} https://$host$request_uri;
             }
+            ${acmeLocation}
           }
         ''}
 
@@ -387,13 +394,6 @@ let
             http3 ${if vhost.http3 then "on" else "off"};
             http3_hq ${if vhost.http3_hq then "on" else "off"};
           ''}
-          ${acmeLocation}
-          ${optionalString (vhost.root != null) "root ${vhost.root};"}
-          ${optionalString (vhost.globalRedirect != null) ''
-            location / {
-              return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
-            }
-          ''}
           ${optionalString hasSSL ''
             ssl_certificate ${vhost.sslCertificate};
             ssl_certificate_key ${vhost.sslCertificateKey};
@@ -408,14 +408,16 @@ let
             ssl_conf_command Options KTLS;
           ''}
 
-          ${optionalString (hasSSL && vhost.quic && vhost.http3)
-            # Advertise that HTTP/3 is available
-          ''
-            add_header Alt-Svc 'h3=":$server_port"; ma=86400';
-          ''}
-
           ${mkBasicAuth vhostName vhost}
 
+          ${optionalString (vhost.root != null) "root ${vhost.root};"}
+
+          ${optionalString (vhost.globalRedirect != null) ''
+            location / {
+              return ${toString vhost.redirectCode} http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
+            }
+          ''}
+          ${acmeLocation}
           ${mkLocations vhost.locations}
 
           ${vhost.extraConfig}
@@ -444,7 +446,7 @@ let
       ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"}
       ${optionalString (config.root != null) "root ${config.root};"}
       ${optionalString (config.alias != null) "alias ${config.alias};"}
-      ${optionalString (config.return != null) "return ${config.return};"}
+      ${optionalString (config.return != null) "return ${toString config.return};"}
       ${config.extraConfig}
       ${optionalString (config.proxyPass != null && config.recommendedProxySettings) "include ${recommendedProxyConfig};"}
       ${mkBasicAuth "sublocation" config}
@@ -467,7 +469,7 @@ let
 
   mkCertOwnershipAssertion = import ../../../security/acme/mk-cert-ownership-assertion.nix;
 
-  oldHTTP2 = versionOlder cfg.package.version "1.25.1";
+  oldHTTP2 = (versionOlder cfg.package.version "1.25.1" && !(cfg.package.pname == "angie" || cfg.package.pname == "angieQuic"));
 in
 
 {
@@ -578,11 +580,13 @@ in
           };
         });
         default = [];
-        example = literalExpression ''[
-          { addr = "10.0.0.12"; proxyProtocol = true; ssl = true; }
-          { addr = "0.0.0.0"; }
-          { addr = "[::0]"; }
-        ]'';
+        example = literalExpression ''
+          [
+            { addr = "10.0.0.12"; proxyProtocol = true; ssl = true; }
+            { addr = "0.0.0.0"; }
+            { addr = "[::0]"; }
+          ]
+        '';
         description = lib.mdDoc ''
           If vhosts do not specify listen, use these addresses by default.
           This option takes precedence over {option}`defaultListenAddresses` and
@@ -642,6 +646,8 @@ in
           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 `nginxMainline`.
+          Supported Nginx forks include `angie`, `openresty` and `tengine`.
+          For HTTP/3 support use `nginxQuic` or `angieQuic`.
         '';
       };
 
@@ -781,6 +787,19 @@ in
         '';
       };
 
+      enableQuicBPF = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enables routing of QUIC packets using eBPF. When enabled, this allows
+          to support QUIC connection migration. The directive is only supported
+          on Linux 5.7+.
+          Note that enabling this option will make nginx run with extended
+          capabilities that are usually limited to processes running as root
+          namely `CAP_SYS_ADMIN` and `CAP_NET_ADMIN`.
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "nginx";
@@ -935,7 +954,7 @@ in
         default = {};
         description = lib.mdDoc ''
           Configure a proxy cache path entry.
-          See <http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
+          See <https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path> for documentation.
         '';
       };
 
@@ -1108,26 +1127,28 @@ in
       }
 
       {
-        assertion = any (host: host.kTLS) (attrValues virtualHosts) -> versionAtLeast cfg.package.version "1.21.4";
+        assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts);
         message = ''
-          services.nginx.virtualHosts.<name>.kTLS requires nginx version
-          1.21.4 or above; see the documentation for services.nginx.package.
+          Options services.nginx.service.virtualHosts.<name>.enableACME and
+          services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
         '';
       }
 
       {
-        assertion = all (host: !(host.enableACME && host.useACMEHost != null)) (attrValues virtualHosts);
+        assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> !(cfg.enableQuicBPF);
         message = ''
-          Options services.nginx.service.virtualHosts.<name>.enableACME and
-          services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
+          services.nginx.enableQuicBPF requires using nginxQuic package,
+          which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or
+          `services.nginx.package = pkgs.angieQuic;`.
         '';
       }
 
       {
-        assertion = cfg.package.pname != "nginxQuic" -> all (host: !host.quic) (attrValues virtualHosts);
+        assertion = cfg.package.pname != "nginxQuic" && cfg.package.pname != "angieQuic" -> all (host: !host.quic) (attrValues virtualHosts);
         message = ''
-          services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic package,
-          which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`.
+          services.nginx.service.virtualHosts.<name>.quic requires using nginxQuic or angie packages,
+          which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;` or
+          `services.nginx.package = pkgs.angieQuic;`.
         '';
       }
 
@@ -1222,8 +1243,8 @@ in
         # New file permissions
         UMask = "0027"; # 0640 / 0750
         # Capabilities
-        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
-        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ] ++ optionals cfg.enableQuicBPF [ "CAP_SYS_ADMIN" "CAP_NET_ADMIN" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ] ++ optionals cfg.enableQuicBPF [ "CAP_SYS_ADMIN" "CAP_NET_ADMIN" ];
         # Security
         NoNewPrivileges = true;
         # Sandboxing (sorted by occurrence in https://www.freedesktop.org/software/systemd/man/systemd.exec.html)
@@ -1248,6 +1269,7 @@ in
         # System Call Filtering
         SystemCallArchitectures = "native";
         SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @setuid" ]
+          ++ optional cfg.enableQuicBPF [ "bpf" ]
           ++ optionals ((cfg.package != pkgs.tengine) && (cfg.package != pkgs.openresty) && (!lib.any (mod: (mod.disableIPC or false)) cfg.package.modules)) [ "~@ipc" ];
       };
     };
@@ -1312,6 +1334,13 @@ in
       nginx.gid = config.ids.gids.nginx;
     };
 
+    boot.kernelModules = optional (versionAtLeast config.boot.kernelPackages.kernel.version "4.17") "tls";
+
+    # do not delete the default temp directories created upon nginx startup
+    systemd.tmpfiles.rules = [
+      "X /tmp/systemd-private-%b-nginx.service-*/tmp/nginx_*"
+    ];
+
     services.logrotate.settings.nginx = mapAttrs (_: mkDefault) {
       files = "/var/log/nginx/*.log";
       frequency = "weekly";
diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix
index 2728852058ea7..2138e551fd434 100644
--- a/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -93,7 +93,7 @@ with lib;
     };
 
     return = mkOption {
-      type = types.nullOr types.str;
+      type = with types; nullOr (oneOf [ str int ]);
       default = null;
       example = "301 http://example.com$request_uri";
       description = lib.mdDoc ''
diff --git a/nixos/modules/services/web-servers/nginx/tailscale-auth.nix b/nixos/modules/services/web-servers/nginx/tailscale-auth.nix
new file mode 100644
index 0000000000000..a2e4d4a30be5c
--- /dev/null
+++ b/nixos/modules/services/web-servers/nginx/tailscale-auth.nix
@@ -0,0 +1,158 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nginx.tailscaleAuth;
+in
+{
+  options.services.nginx.tailscaleAuth = {
+    enable = mkEnableOption (lib.mdDoc "Enable tailscale.nginx-auth, to authenticate nginx users via tailscale.");
+
+    package = lib.mkPackageOptionMD pkgs "tailscale-nginx-auth" {};
+
+    user = mkOption {
+      type = types.str;
+      default = "tailscale-nginx-auth";
+      description = lib.mdDoc "User which runs tailscale-nginx-auth";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "tailscale-nginx-auth";
+      description = lib.mdDoc "Group which runs tailscale-nginx-auth";
+    };
+
+    expectedTailnet = mkOption {
+      default = "";
+      type = types.nullOr types.str;
+      example = "tailnet012345.ts.net";
+      description = lib.mdDoc ''
+        If you want to prevent node sharing from allowing users to access services
+        across tailnets, declare your expected tailnets domain here.
+      '';
+    };
+
+    socketPath = mkOption {
+      default = "/run/tailscale-nginx-auth/tailscale-nginx-auth.sock";
+      type = types.path;
+      description = lib.mdDoc ''
+        Path of the socket listening to nginx authorization requests.
+      '';
+    };
+
+    virtualHosts = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = lib.mdDoc ''
+        A list of nginx virtual hosts to put behind tailscale.nginx-auth
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.tailscale.enable = true;
+    services.nginx.enable = true;
+
+    users.users.${cfg.user} = {
+      isSystemUser = true;
+      inherit (cfg) group;
+    };
+    users.groups.${cfg.group} = { };
+    users.users.${config.services.nginx.user}.extraGroups = [ cfg.group ];
+    systemd.sockets.tailscale-nginx-auth = {
+      description = "Tailscale NGINX Authentication socket";
+      partOf = [ "tailscale-nginx-auth.service" ];
+      wantedBy = [ "sockets.target" ];
+      listenStreams = [ cfg.socketPath ];
+      socketConfig = {
+        SocketMode = "0660";
+        SocketUser = cfg.user;
+        SocketGroup = cfg.group;
+      };
+    };
+
+
+    systemd.services.tailscale-nginx-auth = {
+      description = "Tailscale NGINX Authentication service";
+      after = [ "nginx.service" ];
+      wants = [ "nginx.service" ];
+      requires = [ "tailscale-nginx-auth.socket" ];
+
+      serviceConfig = {
+        ExecStart = "${lib.getExe cfg.package}";
+        RuntimeDirectory = "tailscale-nginx-auth";
+        User = cfg.user;
+        Group = cfg.group;
+
+        BindPaths = [ "/run/tailscale/tailscaled.sock" ];
+
+        CapabilityBoundingSet = "";
+        DeviceAllow = "";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictNamespaces = true;
+        RestrictAddressFamilies = [ "AF_UNIX" ];
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+
+        SystemCallArchitectures = "native";
+        SystemCallErrorNumber = "EPERM";
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation" "~@debug" "~@keyring" "~@memlock" "~@obsolete" "~@privileged" "~@setuid"
+        ];
+      };
+    };
+
+    services.nginx.virtualHosts = genAttrs
+      cfg.virtualHosts
+      (vhost: {
+        locations."/auth" = {
+          extraConfig = ''
+            internal;
+
+            proxy_pass http://unix:${cfg.socketPath};
+            proxy_pass_request_body off;
+
+            # Upstream uses $http_host here, but we are using gixy to check nginx configurations
+            # gixy wants us to use $host: https://github.com/yandex/gixy/blob/master/docs/en/plugins/hostspoofing.md
+            proxy_set_header Host $host;
+            proxy_set_header Remote-Addr $remote_addr;
+            proxy_set_header Remote-Port $remote_port;
+            proxy_set_header Original-URI $request_uri;
+            proxy_set_header X-Scheme                $scheme;
+            proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
+          '';
+        };
+        locations."/".extraConfig = ''
+          auth_request /auth;
+          auth_request_set $auth_user $upstream_http_tailscale_user;
+          auth_request_set $auth_name $upstream_http_tailscale_name;
+          auth_request_set $auth_login $upstream_http_tailscale_login;
+          auth_request_set $auth_tailnet $upstream_http_tailscale_tailnet;
+          auth_request_set $auth_profile_picture $upstream_http_tailscale_profile_picture;
+
+          proxy_set_header X-Webauth-User "$auth_user";
+          proxy_set_header X-Webauth-Name "$auth_name";
+          proxy_set_header X-Webauth-Login "$auth_login";
+          proxy_set_header X-Webauth-Tailnet "$auth_tailnet";
+          proxy_set_header X-Webauth-Profile-Picture "$auth_profile_picture";
+
+          ${lib.optionalString (cfg.expectedTailnet != "") ''proxy_set_header Expected-Tailnet "${cfg.expectedTailnet}";''}
+        '';
+      });
+  };
+
+  meta.maintainers = with maintainers; [ phaer ];
+
+}
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index 7636c1b26115f..ea98439d3823d 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -31,12 +31,15 @@ with lib;
         options = {
           addr = mkOption {
             type = str;
-            description = lib.mdDoc "IP address.";
+            description = lib.mdDoc "Listen address.";
           };
           port = mkOption {
-            type = port;
-            description = lib.mdDoc "Port number.";
-            default = 80;
+            type = types.nullOr port;
+            description = lib.mdDoc ''
+              Port number to listen on.
+              If unset and the listen address is not a socket then nginx defaults to 80.
+            '';
+            default = null;
           };
           ssl = mkOption {
             type = bool;
@@ -60,6 +63,7 @@ with lib;
       example = [
         { addr = "195.154.1.1"; port = 443; ssl = true; }
         { addr = "192.154.1.1"; port = 80; }
+        { addr = "unix:/var/run/nginx.sock"; }
       ];
       description = lib.mdDoc ''
         Listen addresses and ports for this virtual host.
@@ -158,10 +162,11 @@ with lib;
       type = types.bool;
       default = false;
       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
-        `listen` to listen on all interfaces on the respective default
-        ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
+        Whether to add a separate nginx server block that redirects (defaults
+        to 301, configurable with `redirectCode`) all plain HTTP traffic to
+        HTTPS. This will set defaults for `listen` to listen on all interfaces
+        on the respective default ports (80, 443), where the non-SSL listens
+        are used for the redirect vhosts.
       '';
     };
 
@@ -230,9 +235,9 @@ with lib;
         which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`
         and activate the QUIC transport protocol
         `services.nginx.virtualHosts.<name>.quic = true;`.
-        Note that HTTP/3 support is experimental and
-        *not* yet recommended for production.
+        Note that HTTP/3 support is experimental and *not* yet recommended for production.
         Read more at https://quic.nginx.org/
+        HTTP/3 availability must be manually advertised, preferably in each location block.
       '';
     };
 
@@ -245,8 +250,7 @@ with lib;
         which can be achieved by setting `services.nginx.package = pkgs.nginxQuic;`
         and activate the QUIC transport protocol
         `services.nginx.virtualHosts.<name>.quic = true;`.
-        Note that special application protocol support is experimental and
-        *not* yet recommended for production.
+        Note that special application protocol support is experimental and *not* yet recommended for production.
         Read more at https://quic.nginx.org/
       '';
     };
@@ -303,8 +307,20 @@ with lib;
       default = null;
       example = "newserver.example.org";
       description = lib.mdDoc ''
-        If set, all requests for this host are redirected permanently to
-        the given hostname.
+        If set, all requests for this host are redirected (defaults to 301,
+        configurable with `redirectCode`) to the given hostname.
+      '';
+    };
+
+    redirectCode = mkOption {
+      type = types.ints.between 300 399;
+      default = 301;
+      example = 308;
+      description = lib.mdDoc ''
+        HTTP status used by `globalRedirect` and `forceSSL`. Possible usecases
+        include temporary (302, 307) redirects, keeping the request method and
+        body (307, 308), or explicitly resetting the method to GET (303).
+        See <https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections>.
       '';
     };
 
diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix
index 0bd1d5b29b316..4132a97b95437 100644
--- a/nixos/modules/services/web-servers/phpfpm/default.nix
+++ b/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -179,14 +179,7 @@ in {
         '';
       };
 
-      phpPackage = mkOption {
-        type = types.package;
-        default = pkgs.php;
-        defaultText = literalExpression "pkgs.php";
-        description = lib.mdDoc ''
-          The PHP package to use for running the PHP-FPM service.
-        '';
-      };
+      phpPackage = mkPackageOption pkgs "php" { };
 
       phpOptions = mkOption {
         type = types.lines;
diff --git a/nixos/modules/services/web-servers/rustus.nix b/nixos/modules/services/web-servers/rustus.nix
index 878d790e36667..6d3b2e6a65d98 100644
--- a/nixos/modules/services/web-servers/rustus.nix
+++ b/nixos/modules/services/web-servers/rustus.nix
@@ -8,7 +8,7 @@ in
 
   options.services.rustus = {
 
-    enable = mkEnableOption (lib.mdDoc "TUS protocol implementation in Rust.");
+    enable = mkEnableOption (lib.mdDoc "TUS protocol implementation in Rust");
 
     host = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/web-servers/stargazer.nix b/nixos/modules/services/web-servers/stargazer.nix
index f0c3cf8787ebb..18f57363137cf 100644
--- a/nixos/modules/services/web-servers/stargazer.nix
+++ b/nixos/modules/services/web-servers/stargazer.nix
@@ -204,11 +204,9 @@ in
     };
 
     # Create default cert store
-    system.activationScripts.makeStargazerCertDir =
-      lib.optionalAttrs (cfg.store == /var/lib/gemini/certs) ''
-        mkdir -p /var/lib/gemini/certs
-        chown -R ${cfg.user}:${cfg.group} /var/lib/gemini/certs
-      '';
+    systemd.tmpfiles.rules = lib.mkIf (cfg.store == /var/lib/gemini/certs) [
+      ''d /var/lib/gemini/certs - "${cfg.user}" "${cfg.group}" -''
+    ];
 
     users.users = lib.optionalAttrs (cfg.user == "stargazer") {
       stargazer = {
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
index 4d2c36287be69..54ea7b66151f8 100644
--- a/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -1,7 +1,5 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
 
   cfg = config.services.tomcat;
@@ -9,30 +7,22 @@ let
 in
 
 {
-
   meta = {
-    maintainers = with maintainers; [ danbst ];
+    maintainers = with lib.maintainers; [ danbst anthonyroussel ];
   };
 
   ###### interface
 
   options = {
-
     services.tomcat = {
-      enable = mkEnableOption (lib.mdDoc "Apache Tomcat");
+      enable = lib.mkEnableOption (lib.mdDoc "Apache Tomcat");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.tomcat9;
-        defaultText = literalExpression "pkgs.tomcat9";
-        example = lib.literalExpression "pkgs.tomcat9";
-        description = lib.mdDoc ''
-          Which tomcat package to use.
-        '';
+      package = lib.mkPackageOption pkgs "tomcat9" {
+        example = "tomcat10";
       };
 
-      purifyOnStart = mkOption {
-        type = types.bool;
+      purifyOnStart = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc ''
           On startup, the `baseDir` directory is populated with various files,
@@ -43,7 +33,7 @@ in
         '';
       };
 
-      baseDir = mkOption {
+      baseDir = lib.mkOption {
         type = lib.types.path;
         default = "/var/tomcat";
         description = lib.mdDoc ''
@@ -53,64 +43,64 @@ in
         '';
       };
 
-      logDirs = mkOption {
-        default = [];
-        type = types.listOf types.path;
+      logDirs = lib.mkOption {
+        default = [ ];
+        type = lib.types.listOf lib.types.path;
         description = lib.mdDoc "Directories to create in baseDir/logs/";
       };
 
-      extraConfigFiles = mkOption {
-        default = [];
-        type = types.listOf types.path;
+      extraConfigFiles = lib.mkOption {
+        default = [ ];
+        type = lib.types.listOf lib.types.path;
         description = lib.mdDoc "Extra configuration files to pull into the tomcat conf directory";
       };
 
-      extraEnvironment = mkOption {
-        type = types.listOf types.str;
-        default = [];
+      extraEnvironment = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
         example = [ "ENVIRONMENT=production" ];
         description = lib.mdDoc "Environment Variables to pass to the tomcat service";
       };
 
-      extraGroups = mkOption {
-        default = [];
-        type = types.listOf types.str;
+      extraGroups = lib.mkOption {
+        default = [ ];
+        type = lib.types.listOf lib.types.str;
         example = [ "users" ];
         description = lib.mdDoc "Defines extra groups to which the tomcat user belongs.";
       };
 
-      user = mkOption {
-        type = types.str;
+      user = lib.mkOption {
+        type = lib.types.str;
         default = "tomcat";
         description = lib.mdDoc "User account under which Apache Tomcat runs.";
       };
 
-      group = mkOption {
-        type = types.str;
+      group = lib.mkOption {
+        type = lib.types.str;
         default = "tomcat";
         description = lib.mdDoc "Group account under which Apache Tomcat runs.";
       };
 
-      javaOpts = mkOption {
-        type = types.either (types.listOf types.str) types.str;
+      javaOpts = lib.mkOption {
+        type = lib.types.either (lib.types.listOf lib.types.str) lib.types.str;
         default = "";
         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;
+      catalinaOpts = lib.mkOption {
+        type = lib.types.either (lib.types.listOf lib.types.str) lib.types.str;
         default = "";
         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 = [];
+      sharedLibs = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
         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;
+      serverXml = lib.mkOption {
+        type = lib.types.lines;
         default = "";
         description = lib.mdDoc ''
           Verbatim server.xml configuration.
@@ -118,87 +108,74 @@ in
         '';
       };
 
-      commonLibs = mkOption {
-        type = types.listOf types.str;
-        default = [];
+      commonLibs = lib.mkOption {
+        type = lib.types.listOf lib.types.str;
+        default = [ ];
         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;
+      webapps = lib.mkOption {
+        type = lib.types.listOf lib.types.path;
         default = [ tomcat.webapps ];
-        defaultText = literalExpression "[ config.services.tomcat.package.webapps ]";
+        defaultText = lib.literalExpression "[ config.services.tomcat.package.webapps ]";
         description = lib.mdDoc "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
       };
 
-      virtualHosts = mkOption {
-        type = types.listOf (types.submodule {
+      virtualHosts = lib.mkOption {
+        type = lib.types.listOf (lib.types.submodule {
           options = {
-            name = mkOption {
-              type = types.str;
+            name = lib.mkOption {
+              type = lib.types.str;
               description = lib.mdDoc "name of the virtualhost";
             };
-            aliases = mkOption {
-              type = types.listOf types.str;
+            aliases = lib.mkOption {
+              type = lib.types.listOf lib.types.str;
               description = lib.mdDoc "aliases of the virtualhost";
-              default = [];
+              default = [ ];
             };
-            webapps = mkOption {
-              type = types.listOf types.path;
+            webapps = lib.mkOption {
+              type = lib.types.listOf lib.types.path;
               description = lib.mdDoc ''
                 List containing web application WAR files and/or directories containing
                 web applications and configuration files for the virtual host.
               '';
-              default = [];
+              default = [ ];
             };
           };
         });
-        default = [];
+        default = [ ];
         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;
+      logPerVirtualHost = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc "Whether to enable logging per virtual host.";
       };
 
-      jdk = mkOption {
-        type = types.package;
-        default = pkgs.jdk;
-        defaultText = literalExpression "pkgs.jdk";
-        description = lib.mdDoc "Which JDK to use.";
-      };
+      jdk = lib.mkPackageOption pkgs "jdk" { };
 
       axis2 = {
+        enable = lib.mkEnableOption "Apache Axis2 container";
 
-        enable = mkOption {
-          default = false;
-          type = types.bool;
-          description = lib.mdDoc "Whether to enable an Apache Axis2 container";
-        };
-
-        services = mkOption {
-          default = [];
-          type = types.listOf types.str;
+        services = lib.mkOption {
+          default = [ ];
+          type = lib.types.listOf lib.types.str;
           description = lib.mdDoc "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
         };
-
       };
-
     };
-
   };
 
-
   ###### implementation
 
-  config = mkIf config.services.tomcat.enable {
+  config = lib.mkIf config.services.tomcat.enable {
 
     users.groups.tomcat.gid = config.ids.gids.tomcat;
 
     users.users.tomcat =
-      { uid = config.ids.uids.tomcat;
+      {
+        uid = config.ids.uids.tomcat;
         description = "Tomcat user";
         home = "/homeless-shelter";
         group = "tomcat";
@@ -234,7 +211,7 @@ in
           ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
         done
 
-        ${optionalString (cfg.extraConfigFiles != []) ''
+        ${lib.optionalString (cfg.extraConfigFiles != []) ''
           for i in ${toString cfg.extraConfigFiles}; do
             ln -sfn $i ${cfg.baseDir}/conf/`basename $i`
           done
@@ -253,32 +230,32 @@ in
             hostElementForVirtualHost = virtualHost: ''
               <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps"
                     unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
-            '' + concatStrings (innerElementsForVirtualHost virtualHost) + ''
+            '' + lib.concatStrings (innerElementsForVirtualHost virtualHost) + ''
               </Host>
             '';
             innerElementsForVirtualHost = virtualHost:
               (map (alias: ''
                 <Alias>${alias}</Alias>
               '') virtualHost.aliases)
-              ++ (optional cfg.logPerVirtualHost ''
+              ++ (lib.optional cfg.logPerVirtualHost ''
                 <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}"
                        prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/>
               '');
-            hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
-            hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString;
+            hostElementsString = lib.concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
+            hostElementsSedString = lib.replaceStrings ["\n"] ["\\\n"] hostElementsString;
           in ''
             # Create a modified server.xml which also includes all virtual hosts
-            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \
+            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${lib.escapeShellArg hostElementsSedString} \
                   ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
           ''
         }
-        ${optionalString (cfg.logDirs != []) ''
+        ${lib.optionalString (cfg.logDirs != []) ''
           for i in ${toString cfg.logDirs}; do
             mkdir -p ${cfg.baseDir}/logs/$i
             chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/$i
           done
         ''}
-        ${optionalString cfg.logPerVirtualHost (toString (map (h: ''
+        ${lib.optionalString cfg.logPerVirtualHost (toString (map (h: ''
           mkdir -p ${cfg.baseDir}/logs/${h.name}
           chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name}
         '') cfg.virtualHosts))}
@@ -345,7 +322,7 @@ in
 
           # Symlink all the given web applications files or paths into the webapps/ directory
           # of this virtual host
-          for i in "${optionalString (virtualHost ? webapps) (toString virtualHost.webapps)}"; do
+          for i in "${lib.optionalString (virtualHost ? webapps) (toString virtualHost.webapps)}"; do
             if [ -f $i ]; then
               # If the given web application is a file, symlink it into the webapps/ directory
               ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
@@ -368,7 +345,7 @@ in
           done
         '') cfg.virtualHosts)}
 
-        ${optionalString cfg.axis2.enable ''
+        ${lib.optionalString cfg.axis2.enable ''
           # Copy the Axis2 web application
           cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps
 
@@ -405,10 +382,10 @@ in
       serviceConfig = {
         Type = "forking";
         PermissionsStartOnly = true;
-        PIDFile="/run/tomcat/tomcat.pid";
+        PIDFile = "/run/tomcat/tomcat.pid";
         RuntimeDirectory = "tomcat";
         User = cfg.user;
-        Environment=[
+        Environment = [
           "CATALINA_BASE=${cfg.baseDir}"
           "CATALINA_PID=/run/tomcat/tomcat.pid"
           "JAVA_HOME='${cfg.jdk}'"
diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix
index 42fb95a52200d..fc9eb504ebf81 100644
--- a/nixos/modules/services/web-servers/traefik.nix
+++ b/nixos/modules/services/web-servers/traefik.nix
@@ -126,12 +126,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      default = pkgs.traefik;
-      defaultText = literalExpression "pkgs.traefik";
-      type = types.package;
-      description = lib.mdDoc "Traefik package to use.";
-    };
+    package = mkPackageOption pkgs "traefik" { };
 
     environmentFiles = mkOption {
       default = [];
@@ -149,6 +144,7 @@ in {
 
     systemd.services.traefik = {
       description = "Traefik web server";
+      wants = [ "network-online.target" ];
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       startLimitIntervalSec = 86400;
diff --git a/nixos/modules/services/web-servers/ttyd.nix b/nixos/modules/services/web-servers/ttyd.nix
index 3b1d87ccb483e..e545869ca4320 100644
--- a/nixos/modules/services/web-servers/ttyd.nix
+++ b/nixos/modules/services/web-servers/ttyd.nix
@@ -180,10 +180,11 @@ in
         # Runs login which needs to be run as root
         # login: Cannot possibly work without effective root
         User = "root";
+        LoadCredential = lib.optionalString (cfg.passwordFile != null) "TTYD_PASSWORD_FILE:${cfg.passwordFile}";
       };
 
       script = if cfg.passwordFile != null then ''
-        PASSWORD=$(cat ${escapeShellArg cfg.passwordFile})
+        PASSWORD=$(cat "$CREDENTIALS_DIRECTORY/TTYD_PASSWORD_FILE")
         ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
           --credential ${escapeShellArg cfg.username}:"$PASSWORD" \
           ${pkgs.shadow}/bin/login
diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix
index 1515779c90649..a5f1a872ce81c 100644
--- a/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixos/modules/services/web-servers/unit/default.nix
@@ -11,12 +11,7 @@ in {
   options = {
     services.unit = {
       enable = mkEnableOption (lib.mdDoc "Unit App Server");
-      package = mkOption {
-        type = types.package;
-        default = pkgs.unit;
-        defaultText = literalExpression "pkgs.unit";
-        description = lib.mdDoc "Unit package to use.";
-      };
+      package = mkPackageOption pkgs "unit" { };
       user = mkOption {
         type = types.str;
         default = "unit";
diff --git a/nixos/modules/services/web-servers/varnish/default.nix b/nixos/modules/services/web-servers/varnish/default.nix
index d7f19be0cec47..857dd64c01beb 100644
--- a/nixos/modules/services/web-servers/varnish/default.nix
+++ b/nixos/modules/services/web-servers/varnish/default.nix
@@ -15,14 +15,7 @@ in
 
       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 = lib.mdDoc ''
-          The package to use
-        '';
-      };
+      package = mkPackageOption pkgs "varnish" { };
 
       http_address = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixos/modules/services/x11/desktop-managers/budgie.nix
index a4f8bd5051ecc..de4b2c0e50f5f 100644
--- a/nixos/modules/services/x11/desktop-managers/budgie.nix
+++ b/nixos/modules/services/x11/desktop-managers/budgie.nix
@@ -202,6 +202,7 @@ in {
     xdg.portal.extraPortals = with pkgs; [
       xdg-desktop-portal-gtk # provides a XDG Portals implementation.
     ];
+    xdg.portal.configPackages = mkDefault [ pkgs.budgie.budgie-desktop ];
 
     services.geoclue2.enable = mkDefault true; # for BCC's Privacy > Location Services panel.
     services.upower.enable = config.powerManagement.enable; # for Budgie's Status Indicator and BCC's Power panel.
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index bb42c52b69ca4..f5a6c05865c47 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -79,20 +79,19 @@ in
           package = mkDefault pkgs.cinnamon.mint-cursor-themes;
         };
       };
-      services.xserver.displayManager.sessionCommands = ''
-        if test "$XDG_CURRENT_DESKTOP" = "Cinnamon"; then
-            true
-            ${concatMapStrings (p: ''
-              if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
-                export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
-              fi
-
-              if [ -d "${p}/lib/girepository-1.0" ]; then
-                export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
-                export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
-              fi
-            '') cfg.sessionPath}
-        fi
+
+      # Have to take care of GDM + Cinnamon on Wayland users
+      environment.extraInit = ''
+        ${concatMapStrings (p: ''
+          if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+            export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+          fi
+
+          if [ -d "${p}/lib/girepository-1.0" ]; then
+            export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+            export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+          fi
+        '') cfg.sessionPath}
       '';
 
       # Default services
@@ -200,6 +199,8 @@ in
         })
       ];
 
+      xdg.portal.configPackages = mkDefault [ pkgs.cinnamon.cinnamon-common ];
+
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
 
@@ -221,7 +222,7 @@ in
 
       # Default Fonts
       fonts.packages = with pkgs; [
-        source-code-pro # Default monospace font in 3.32
+        dejavu_fonts # Default monospace font in LMDE 6+
         ubuntu_font_family # required for default theme
       ];
     })
diff --git a/nixos/modules/services/x11/desktop-managers/deepin.nix b/nixos/modules/services/x11/desktop-managers/deepin.nix
index b2369e2426f82..7fdd50b1ed263 100644
--- a/nixos/modules/services/x11/desktop-managers/deepin.nix
+++ b/nixos/modules/services/x11/desktop-managers/deepin.nix
@@ -15,7 +15,7 @@ in
   options = {
 
     services.xserver.desktopManager.deepin = {
-      enable = mkEnableOption (lib.mdDoc "Enable Deepin desktop manager");
+      enable = mkEnableOption (lib.mdDoc "Deepin desktop manager");
       extraGSettingsOverrides = mkOption {
         default = "";
         type = types.lines;
@@ -38,8 +38,8 @@ in
 
   config = mkIf cfg.enable
     {
-      services.xserver.displayManager.sessionPackages = [ pkgs.deepin.startdde ];
-      services.xserver.displayManager.defaultSession = mkDefault "deepin";
+      services.xserver.displayManager.sessionPackages = [ pkgs.deepin.dde-session ];
+      services.xserver.displayManager.defaultSession = mkDefault "dde-x11";
 
       # Update the DBus activation environment after launching the desktop manager.
       services.xserver.displayManager.sessionCommands = ''
@@ -78,6 +78,9 @@ in
         })
       ];
 
+      # https://github.com/NixOS/nixpkgs/pull/247766#issuecomment-1722839259
+      xdg.portal.config.deepin.default = mkDefault [ "gtk" ];
+
       environment.sessionVariables = {
         NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
         DDE_POLKIT_AGENT_PLUGINS_DIRS = [ "${pkgs.deepin.dpa-ext-gnomekeyring}/lib/polkit-1-dde/plugins" ];
@@ -90,6 +93,9 @@ in
         "/lib/dde-file-manager"
         "/share/backgrounds"
         "/share/wallpapers"
+        "/share/dde-daemon"
+        "/share/dsg"
+        "/share/deepin-themes"
       ];
 
       environment.etc = {
@@ -135,19 +141,25 @@ in
             libsForQt5.kde-gtk-config # deepin-api/gtk-thumbnailer need
             libsForQt5.kglobalaccel
             xsettingsd # lightdm-deepin-greeter
+            dtkcommon
+            dtkcore
+            dtkgui
+            dtkwidget
+            dtkdeclarative
             qt5platform-plugins
             deepin-pw-check
             deepin-turbo
 
             dde-account-faces
             deepin-icon-theme
+            deepin-desktop-theme
             deepin-sound-theme
             deepin-gtk-theme
             deepin-wallpapers
 
             startdde
             dde-dock
-            dde-launcher
+            dde-launchpad
             dde-session-ui
             dde-session-shell
             dde-file-manager
@@ -159,8 +171,12 @@ in
             dpa-ext-gnomekeyring
             deepin-desktop-schemas
             deepin-terminal
-            dde-kwin
             deepin-kwin
+            dde-session
+            dde-widgets
+            dde-appearance
+            dde-application-manager
+            deepin-service-manager
           ];
           optionalPackages = [
             onboard # dde-dock plugin
@@ -184,24 +200,33 @@ in
 
       services.dbus.packages = with pkgs.deepin; [
         dde-dock
-        dde-launcher
+        dde-launchpad
         dde-session-ui
         dde-session-shell
         dde-file-manager
         dde-control-center
         dde-calendar
         dde-clipboard
-        dde-kwin
         deepin-kwin
         deepin-pw-check
+        dde-widgets
+        dde-session
+        dde-appearance
+        dde-application-manager
+        deepin-service-manager
       ];
 
       systemd.packages = with pkgs.deepin; [
-        dde-launcher
+        dde-launchpad
         dde-file-manager
         dde-calendar
         dde-clipboard
         deepin-kwin
+        dde-appearance
+        dde-widgets
+        dde-session
+        dde-application-manager
+        deepin-service-manager
       ];
     };
 }
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 1512b5fdf8a0a..28dd408c923c8 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -63,7 +63,7 @@ in
         # make available for D-BUS user services
         #export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}:${config.system.path}/share:${e.efl}/share
 
-        # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+        # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
         ${pkgs.xdg-user-dirs}/bin/xdg-user-dirs-update
       fi
     '';
@@ -90,7 +90,7 @@ in
         };
     };
 
-    environment.etc."X11/xkb".source = xcfg.xkbDir;
+    environment.etc."X11/xkb".source = xcfg.xkb.dir;
 
     fonts.packages = [ pkgs.dejavu_fonts pkgs.ubuntu_font_family ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.md b/nixos/modules/services/x11/desktop-managers/gnome.md
index d9e75bfe6bddd..aa36f66970ec4 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.md
+++ b/nixos/modules/services/x11/desktop-managers/gnome.md
@@ -145,7 +145,7 @@ services.xserver.desktopManager.gnome = {
 
     # Favorite apps in gnome-shell
     [org.gnome.shell]
-    favorite-apps=['org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop']
+    favorite-apps=['org.gnome.Console.desktop', 'org.gnome.Nautilus.desktop']
   '';
 
   extraGSettingsOverridePackages = [
diff --git a/nixos/modules/services/x11/desktop-managers/gnome.nix b/nixos/modules/services/x11/desktop-managers/gnome.nix
index fecdd86deb868..2cf9bc2eac37e 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome.nix
@@ -19,7 +19,7 @@ let
 
   defaultFavoriteAppsOverride = ''
     [org.gnome.shell]
-    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' ]
+    favorite-apps=[ 'org.gnome.Epiphany.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Calendar.desktop', 'org.gnome.Music.desktop', 'org.gnome.Nautilus.desktop' ]
   '';
 
   nixos-background-light = pkgs.nixos-artwork.wallpapers.simple-blue;
@@ -229,7 +229,7 @@ in
         panelModulePackages = mkOption {
           default = [ pkgs.gnome.gnome-applets ];
           defaultText = literalExpression "[ pkgs.gnome.gnome-applets ]";
-          type = types.listOf types.path;
+          type = types.listOf types.package;
           description = lib.mdDoc ''
             Packages containing modules that should be made available to `gnome-panel` (usually for applets).
 
@@ -282,9 +282,6 @@ in
 
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
-
-       # If gnome is installed, build vim for gtk3 too.
-      nixpkgs.config.vim.gui = "gtk3";
     })
 
     (mkIf flashbackEnabled {
@@ -297,8 +294,7 @@ in
           map
             (wm:
               pkgs.gnome.gnome-flashback.mkSessionForWm {
-                inherit (wm) wmName wmLabel wmCommand enableGnomePanel;
-                inherit (cfg.flashback) panelModulePackages;
+                inherit (wm) wmName wmLabel wmCommand;
               }
             ) flashbackWms;
 
@@ -310,10 +306,16 @@ in
         gnome-flashback
       ] ++ map gnome-flashback.mkSystemdTargetForWm flashbackWms;
 
-      # gnome-panel needs these for menu applet
-      environment.sessionVariables.XDG_DATA_DIRS = [ "${pkgs.gnome.gnome-flashback}/share" ];
-      # TODO: switch to sessionVariables (resolve conflict)
-      environment.variables.XDG_CONFIG_DIRS = [ "${pkgs.gnome.gnome-flashback}/etc/xdg" ];
+      environment.systemPackages = with pkgs.gnome; [
+        gnome-flashback
+        (gnome-panel-with-modules.override {
+          panelModulePackages = cfg.flashback.panelModulePackages;
+        })
+      ]
+      # For /share/applications/${wmName}.desktop
+      ++ (map (wm: gnome-flashback.mkWmApplication { inherit (wm) wmName wmLabel wmCommand; }) flashbackWms)
+      # For /share/gnome-session/sessions/gnome-flashback-${wmName}.session
+      ++ (map (wm: gnome-flashback.mkGnomeSession { inherit (wm) wmName wmLabel enableGnomePanel; }) flashbackWms);
     })
 
     (mkIf serviceCfg.core-os-services.enable {
@@ -351,6 +353,7 @@ in
           buildPortalsInGnome = false;
         })
       ];
+      xdg.portal.configPackages = mkDefault [ pkgs.gnome.gnome-session ];
 
       networking.networkmanager.enable = mkDefault true;
 
@@ -446,29 +449,26 @@ in
             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/
+            pkgs.xdg-user-dirs # Update user dirs as described in https://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
+    # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/-/blob/gnome-45/elements/core/meta-gnome-core-utilities.bst
     (mkIf serviceCfg.core-utilities.enable {
       environment.systemPackages =
         with pkgs.gnome;
         utils.removePackagesByName
           ([
             baobab
-            cheese
-            eog
             epiphany
             pkgs.gnome-text-editor
             gnome-calculator
@@ -481,12 +481,13 @@ in
             gnome-logs
             gnome-maps
             gnome-music
-            pkgs.gnome-photos
             gnome-system-monitor
             gnome-weather
+            pkgs.loupe
             nautilus
             pkgs.gnome-connections
             simple-scan
+            pkgs.snapshot
             totem
             yelp
           ] ++ lib.optionals config.services.flatpak.enable [
diff --git a/nixos/modules/services/x11/desktop-managers/kodi.nix b/nixos/modules/services/x11/desktop-managers/kodi.nix
index 43904cd00e840..452f571d49e67 100644
--- a/nixos/modules/services/x11/desktop-managers/kodi.nix
+++ b/nixos/modules/services/x11/desktop-managers/kodi.nix
@@ -15,14 +15,8 @@ in
         description = lib.mdDoc "Enable the kodi multimedia center.";
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.kodi;
-        defaultText = literalExpression "pkgs.kodi";
-        example = literalExpression "pkgs.kodi.withPackages (p: with p; [ jellyfin pvr-iptvsimple vfs-sftp ])";
-        description = lib.mdDoc ''
-          Package that should be used for Kodi.
-        '';
+      package = mkPackageOption pkgs "kodi" {
+        example = "kodi.withPackages (p: with p; [ jellyfin pvr-iptvsimple vfs-sftp ])";
       };
     };
   };
diff --git a/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index b69da41c9fc9c..50ad72dc7388d 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -70,6 +70,9 @@ in
     services.xserver.libinput.enable = mkDefault true;
 
     xdg.portal.lxqt.enable = true;
+
+    # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050804
+    xdg.portal.config.lxqt.default = mkDefault [ "lxqt" "gtk" ];
   };
 
 }
diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix
index c93f120bed7f5..f535a1d298b9f 100644
--- a/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -77,6 +77,8 @@ in
 
     security.pam.services.mate-screensaver.unixAuth = true;
 
+    xdg.portal.configPackages = mkDefault [ pkgs.mate.mate-desktop ];
+
     environment.pathsToLink = [ "/share" ];
   };
 
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index eef7aa14057ea..59bc142eeb7f9 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -201,7 +201,7 @@ in
         onboard
         orca # elementary/greeter#668
         sound-theme-freedesktop
-        xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+        xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
       ]) ++ (with pkgs.pantheon; [
         # Artwork
         elementary-gtk-theme
@@ -229,9 +229,6 @@ in
 
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [
-        # Some Pantheon apps enforce portal usage, we need this for e.g. notifications.
-        # Currently we have buildPortalsInGnome enabled, if you run into issues related
-        # to https://github.com/flatpak/xdg-desktop-portal/issues/656 please report to us.
         pkgs.xdg-desktop-portal-gtk
       ] ++ (with pkgs.pantheon; [
         elementary-files
@@ -239,6 +236,8 @@ in
         xdg-desktop-portal-pantheon
       ]);
 
+      xdg.portal.configPackages = mkDefault [ pkgs.pantheon.elementary-default-settings ];
+
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
 
diff --git a/nixos/modules/services/x11/desktop-managers/phosh.nix b/nixos/modules/services/x11/desktop-managers/phosh.nix
index e4cd9fd99e40c..5392ab73aeb8f 100644
--- a/nixos/modules/services/x11/desktop-managers/phosh.nix
+++ b/nixos/modules/services/x11/desktop-managers/phosh.nix
@@ -135,15 +135,7 @@ in
         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.
-        '';
-      };
+      package = mkPackageOption pkgs "phosh" { };
 
       user = mkOption {
         description = lib.mdDoc "The user to run the Phosh service.";
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 15a510fd8f962..fc9de2500ba46 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -26,12 +26,10 @@ let
         emptyValue.value = {};
       };
 
-  libsForQt5 = pkgs.plasma5Packages;
-  inherit (libsForQt5) kdeGear kdeFrameworks plasma5;
   inherit (lib)
-    getBin optionalString literalExpression
+    getBin optionalAttrs literalExpression
     mkRemovedOptionModule mkRenamedOptionModule
-    mkDefault mkIf mkMerge mkOption mkPackageOptionMD types;
+    mkDefault mkIf mkMerge mkOption mkPackageOption types;
 
   activationScript = ''
     ${set_XDG_CONFIG_HOME}
@@ -65,7 +63,7 @@ let
     # recognize that software that has been removed.
     rm -fv $HOME/.cache/ksycoca*
 
-    ${libsForQt5.kservice}/bin/kbuildsycoca5
+    ${pkgs.plasma5Packages.kservice}/bin/kbuildsycoca5
   '';
 
   set_XDG_CONFIG_HOME = ''
@@ -108,7 +106,7 @@ in
         default = true;
       };
 
-      notoPackage = mkPackageOptionMD pkgs "Noto fonts" {
+      notoPackage = mkPackageOption pkgs "Noto fonts" {
         default = [ "noto-fonts" ];
         example = "noto-fonts-lgc-plus";
       };
@@ -172,29 +170,23 @@ in
     (mkIf (cfg.enable || cfg.mobile.enable || cfg.bigscreen.enable) {
 
       security.wrappers = {
-        kscreenlocker_greet = {
-          setuid = true;
+        kwin_wayland = {
           owner = "root";
           group = "root";
-          source = "${getBin libsForQt5.kscreenlocker}/libexec/kscreenlocker_greet";
+          capabilities = "cap_sys_nice+ep";
+          source = "${getBin pkgs.plasma5Packages.kwin}/bin/kwin_wayland";
         };
+      } // optionalAttrs (!cfg.runUsingSystemd) {
         start_kdeinit = {
           setuid = true;
           owner = "root";
           group = "root";
-          source = "${getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
-        };
-        kwin_wayland = {
-          owner = "root";
-          group = "root";
-          capabilities = "cap_sys_nice+ep";
-          source = "${getBin plasma5.kwin}/bin/kwin_wayland";
+          source = "${getBin pkgs.plasma5Packages.kinit}/libexec/kf5/start_kdeinit";
         };
       };
 
       environment.systemPackages =
-        with libsForQt5;
-        with plasma5; with kdeGear; with kdeFrameworks;
+        with pkgs.plasma5Packages;
         let
           requiredPackages = [
             frameworkintegration
@@ -289,8 +281,8 @@ in
         ++ utils.removePackagesByName optionalPackages config.environment.plasma5.excludePackages
 
         # Phonon audio backend
-        ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
-        ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc
+        ++ lib.optional (cfg.phononBackend == "gstreamer") pkgs.plasma5Packages.phonon-backend-gstreamer
+        ++ lib.optional (cfg.phononBackend == "vlc") pkgs.plasma5Packages.phonon-backend-vlc
 
         # Optional hardware support features
         ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt pkgs.openobex pkgs.obexftp ]
@@ -300,13 +292,13 @@ in
         ++ lib.optional config.powerManagement.enable powerdevil
         ++ lib.optional config.services.colord.enable pkgs.colord-kde
         ++ lib.optional config.services.hardware.bolt.enable pkgs.plasma5Packages.plasma-thunderbolt
-        ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ]
+        ++ lib.optional config.services.samba.enable kdenetwork-filesharing
         ++ lib.optional config.services.xserver.wacom.enable pkgs.wacomtablet
         ++ lib.optional config.services.flatpak.enable flatpak-kcm;
 
       # Extra services for D-Bus activation
       services.dbus.packages = [
-        plasma5.kactivitymanagerd
+        pkgs.plasma5Packages.kactivitymanagerd
       ];
 
       environment.pathsToLink = [
@@ -314,7 +306,7 @@ in
         "/share"
       ];
 
-      environment.etc."X11/xkb".source = xcfg.xkbDir;
+      environment.etc."X11/xkb".source = xcfg.xkb.dir;
 
       environment.sessionVariables = {
         PLASMA_USE_QT_SCALING = mkIf cfg.useQtScaling "1";
@@ -339,10 +331,11 @@ in
         serif = [ "Noto Serif" ];
       };
 
-      programs.ssh.askPassword = mkDefault "${plasma5.ksshaskpass.out}/bin/ksshaskpass";
+      programs.ssh.askPassword = mkDefault "${pkgs.plasma5Packages.ksshaskpass.out}/bin/ksshaskpass";
 
       # Enable helpful DBus services.
       services.accounts-daemon.enable = true;
+      programs.dconf.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;
@@ -376,7 +369,8 @@ in
       };
 
       xdg.portal.enable = true;
-      xdg.portal.extraPortals = [ plasma5.xdg-desktop-portal-kde ];
+      xdg.portal.extraPortals = [ pkgs.plasma5Packages.xdg-desktop-portal-kde ];
+      xdg.portal.configPackages = mkDefault [ pkgs.plasma5Packages.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;
@@ -384,7 +378,7 @@ in
       # Update the start menu for each user that is currently logged in
       system.userActivationScripts.plasmaSetup = activationScript;
 
-      nixpkgs.config.firefox.enablePlasmaBrowserIntegration = true;
+      programs.firefox.nativeMessagingHosts.packages = [ pkgs.plasma5Packages.plasma-browser-integration ];
     })
 
     (mkIf (cfg.kwinrc != {}) {
@@ -407,15 +401,14 @@ in
         ''
       ];
 
-      services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-workspace ];
+      services.xserver.displayManager.sessionPackages = [ pkgs.plasma5Packages.plasma-workspace ];
       # Default to be `plasma` (X11) instead of `plasmawayland`, since plasma wayland currently has
       # many tiny bugs.
       # See: https://github.com/NixOS/nixpkgs/issues/143272
       services.xserver.displayManager.defaultSession = mkDefault "plasma";
 
       environment.systemPackages =
-        with libsForQt5;
-        with plasma5; with kdeGear; with kdeFrameworks;
+        with pkgs.plasma5Packages;
         let
           requiredPackages = [
             ksystemstats
@@ -451,7 +444,7 @@ in
           script = ''
             ${set_XDG_CONFIG_HOME}
 
-            ${kdeFrameworks.kconfig}/bin/kwriteconfig5 \
+            ${pkgs.plasma5Packages.kconfig}/bin/kwriteconfig5 \
               --file startkderc --group General --key systemdBoot ${lib.boolToString cfg.runUsingSystemd}
           '';
         };
@@ -479,8 +472,7 @@ in
       ];
 
       environment.systemPackages =
-        with libsForQt5;
-        with plasma5; with kdeApplications; with kdeFrameworks;
+        with pkgs.plasma5Packages;
         [
           # Basic packages without which Plasma Mobile fails to work properly.
           plasma-mobile
@@ -539,7 +531,7 @@ in
         };
       };
 
-      services.xserver.displayManager.sessionPackages = [ pkgs.libsForQt5.plasma5.plasma-mobile ];
+      services.xserver.displayManager.sessionPackages = [ pkgs.plasma5Packages.plasma-mobile ];
     })
 
     # Plasma Bigscreen
diff --git a/nixos/modules/services/x11/desktop-managers/retroarch.nix b/nixos/modules/services/x11/desktop-managers/retroarch.nix
index 5552f37612a29..9db637191b542 100644
--- a/nixos/modules/services/x11/desktop-managers/retroarch.nix
+++ b/nixos/modules/services/x11/desktop-managers/retroarch.nix
@@ -8,12 +8,8 @@ in {
   options.services.xserver.desktopManager.retroarch = {
     enable = mkEnableOption (lib.mdDoc "RetroArch");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.retroarch;
-      defaultText = literalExpression "pkgs.retroarch";
-      example = literalExpression "pkgs.retroarch-full";
-      description = lib.mdDoc "RetroArch package to use.";
+    package = mkPackageOption pkgs "retroarch" {
+      example = "retroarch-full";
     };
 
     extraArgs = mkOption {
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 191b3690c02f3..e28486bcc12d8 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -178,5 +178,7 @@ in
     ]) excludePackages;
 
     security.pam.services.xfce4-screensaver.unixAuth = cfg.enableScreensaver;
+
+    xdg.portal.configPackages = mkDefault [ pkgs.xfce.xfce4-session ];
   };
 }
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 1f08ded7c96fa..3e2d5780a5cb1 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -96,7 +96,7 @@ let
       )}
 
       # Speed up application start by 50-150ms according to
-      # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
+      # https://kdemonkey.blogspot.com/2008/04/magic-trick.html
       compose_cache="''${XCOMPOSECACHE:-$HOME/.compose-cache}"
       mkdir -p "$compose_cache"
       # To avoid accidentally deleting a wrongly set up XCOMPOSECACHE directory,
@@ -514,7 +514,7 @@ in
 
     # Make xsessions and wayland sessions available in XDG_DATA_DIRS
     # as some programs have behavior that depends on them being present
-    environment.sessionVariables.XDG_DATA_DIRS = [
+    environment.sessionVariables.XDG_DATA_DIRS = lib.mkIf (cfg.displayManager.sessionPackages != [ ]) [
       "${cfg.displayManager.sessionData.desktops}/share"
     ];
   };
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index e6923bcbb56c0..400e5601dc59a 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -97,6 +97,19 @@ in
         type = types.bool;
       };
 
+      banner = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        example = ''
+          foo
+          bar
+          baz
+        '';
+        description = lib.mdDoc ''
+          Optional message to display on the login screen.
+        '';
+      };
+
       settings = mkOption {
         type = settingsFormat.type;
         default = { };
@@ -238,6 +251,11 @@ in
         sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0;
         sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0;
       };
+    }] ++ lib.optionals (cfg.gdm.banner != null) [{
+      settings."org/gnome/login-screen" = {
+        banner-message-enable = true;
+        banner-message-text = cfg.gdm.banner;
+      };
     }] ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ];
 
     # Use AutomaticLogin if delay is zero, because it's immediate.
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 8d6bfa98a7e4b..dede7680ecb3a 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
@@ -61,7 +61,7 @@ in
 
     services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
 
-    nixpkgs.config.lightdm-tiny-greeter.conf =
+    services.xserver.displayManager.lightdm.greeter =
     let
       configHeader = ''
         #include <gtk/gtk.h>
@@ -69,13 +69,11 @@ in
         static const char *pass_text = "${cfg.label.pass}";
         static const char *session = "${dmcfg.defaultSession}";
       '';
+      config = optionalString (cfg.extraConfig != "") (configHeader + cfg.extraConfig);
+      package = pkgs.lightdm-tiny-greeter.override { conf = config; };
     in
-      optionalString (cfg.extraConfig != "")
-        (configHeader + cfg.extraConfig);
-
-    services.xserver.displayManager.lightdm.greeter =
       mkDefault {
-        package = pkgs.lightdm-tiny-greeter.xgreeters;
+        package = package.xgreeters;
         name = "lightdm-tiny-greeter";
       };
 
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 47e60236eaebc..0576619cc8d28 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -7,7 +7,7 @@ let
   cfg = dmcfg.sddm;
   xEnv = config.systemd.services.display-manager.environment;
 
-  sddm = pkgs.libsForQt5.sddm;
+  sddm = cfg.package;
 
   iniFmt = pkgs.formats.ini { };
 
@@ -33,6 +33,8 @@ let
 
       # Implementation is done via pkgs/applications/display-managers/sddm/sddm-default-session.patch
       DefaultSession = optionalString (dmcfg.defaultSession != null) "${dmcfg.defaultSession}.desktop";
+
+      DisplayServer = if cfg.wayland.enable then "wayland" else "x11";
     };
 
     Theme = {
@@ -62,6 +64,7 @@ let
     Wayland = {
       EnableHiDPI = cfg.enableHidpi;
       SessionDir = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
+      CompositorCommand = lib.optionalString cfg.wayland.enable cfg.wayland.compositorCommand;
     };
   } // lib.optionalAttrs dmcfg.autoLogin.enable {
     Autologin = {
@@ -105,6 +108,8 @@ in
         '';
       };
 
+      package = mkPackageOption pkgs [ "plasma5Packages" "sddm" ] {};
+
       enableHidpi = mkOption {
         type = types.bool;
         default = true;
@@ -184,6 +189,32 @@ in
           '';
         };
       };
+
+      # Experimental Wayland support
+      wayland = {
+        enable = mkEnableOption "experimental Wayland support";
+
+        compositorCommand = mkOption {
+          type = types.str;
+          internal = true;
+
+          # This is basically the upstream default, but with Weston referenced by full path
+          # and the configuration generated from NixOS options.
+          default = let westonIni = (pkgs.formats.ini {}).generate "weston.ini" {
+              libinput = {
+                enable-tap = xcfg.libinput.mouse.tapping;
+                left-handed = xcfg.libinput.mouse.leftHanded;
+              };
+              keyboard = {
+                keymap_model = xcfg.xkb.model;
+                keymap_layout = xcfg.xkb.layout;
+                keymap_variant = xcfg.xkb.variant;
+                keymap_options = xcfg.xkb.options;
+              };
+            }; in "${pkgs.weston}/bin/weston --shell=fullscreen-shell.so -c ${westonIni}";
+          description = lib.mdDoc "Command used to start the selected compositor";
+        };
+      };
     };
   };
 
diff --git a/nixos/modules/services/x11/display-managers/xpra.nix b/nixos/modules/services/x11/display-managers/xpra.nix
index cb78f52d9b689..0861530f21e84 100644
--- a/nixos/modules/services/x11/display-managers/xpra.nix
+++ b/nixos/modules/services/x11/display-managers/xpra.nix
@@ -60,11 +60,11 @@ in
       VertRefresh 1.0 - 200.0
       #To add your own modes here, use a modeline calculator, like:
       # cvt:
-      # http://www.x.org/archive/X11R7.5/doc/man/man1/cvt.1.html
+      # https://www.x.org/archive/X11R7.5/doc/man/man1/cvt.1.html
       # xtiming:
-      # http://xtiming.sourceforge.net/cgi-bin/xtiming.pl
+      # https://xtiming.sourceforge.net/cgi-bin/xtiming.pl
       # gtf:
-      # http://gtf.sourceforge.net/
+      # https://gtf.sourceforge.net/
       #This can be used to get a specific DPI, but only for the default resolution:
       #DisplaySize 508 317
       #NOTE: the highest modes will not work without increasing the VideoRam
diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix
index 1f48713a68ddd..ab7e39739eeb3 100644
--- a/nixos/modules/services/x11/extra-layouts.nix
+++ b/nixos/modules/services/x11/extra-layouts.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  layouts = config.services.xserver.extraLayouts;
+  layouts = config.services.xserver.xkb.extraLayouts;
 
   layoutOpts = {
     options = {
@@ -15,10 +15,10 @@ let
       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)
-        '';
+          lib.mdDoc ''
+            A list of languages provided by the layout.
+            (Use ISO 639-2 codes, for example: "eng" for english)
+          '';
       };
 
       compatFile = mkOption {
@@ -80,29 +80,37 @@ let
   };
 
   xkb_patched = pkgs.xorg.xkeyboardconfig_custom {
-    layouts = config.services.xserver.extraLayouts;
+    layouts = config.services.xserver.xkb.extraLayouts;
   };
 
 in
 
 {
 
+  imports = [
+    (lib.mkRenamedOptionModuleWith {
+      sinceRelease = 2311;
+      from = [ "services" "xserver" "extraLayouts" ];
+      to = [ "services" "xserver" "xkb" "extraLayouts" ];
+    })
+  ];
+
   ###### interface
 
-  options.services.xserver = {
+  options.services.xserver.xkb = {
     extraLayouts = mkOption {
       type = types.attrsOf (types.submodule layoutOpts);
-      default = {};
+      default = { };
       example = literalExpression
-      ''
-        {
-          mine = {
-            description = "My custom xkb layout.";
-            languages = [ "eng" ];
-            symbolsFile = /path/to/my/layout;
-          };
-        }
-      '';
+        ''
+          {
+            mine = {
+              description = "My custom xkb layout.";
+              languages = [ "eng" ];
+              symbolsFile = /path/to/my/layout;
+            };
+          }
+        '';
       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:
@@ -121,11 +129,11 @@ in
     environment.sessionVariables = {
       # runtime override supported by multiple libraries e. g. libxkbcommon
       # https://xkbcommon.org/doc/current/group__include-path.html
-      XKB_CONFIG_ROOT = config.services.xserver.xkbDir;
+      XKB_CONFIG_ROOT = config.services.xserver.xkb.dir;
     };
 
     services.xserver = {
-      xkbDir = "${xkb_patched}/etc/X11/xkb";
+      xkb.dir = "${xkb_patched}/etc/X11/xkb";
       exportConfiguration = config.services.xserver.displayManager.startx.enable
         || config.services.xserver.displayManager.sx.enable;
     };
diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index d2a5b5895e0aa..0ea21eb1dce3a 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -130,9 +130,9 @@ let cfg = config.services.xserver.libinput;
         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.
+            Enables or disables horizontal scrolling. When disabled, this driver will discard any
+            horizontal scroll events from libinput. This does not disable horizontal scroll events
+            from libinput; it merely discards the horizontal axis from any scroll events.
           '';
       };
 
diff --git a/nixos/modules/services/x11/imwheel.nix b/nixos/modules/services/x11/imwheel.nix
index 133e64c65cdd7..bd2bcb7bcd06c 100644
--- a/nixos/modules/services/x11/imwheel.nix
+++ b/nixos/modules/services/x11/imwheel.nix
@@ -37,7 +37,7 @@ in
             Window class translation rules.
             /etc/X11/imwheelrc is generated based on this config
             which means this config is global for all users.
-            See [official man pages](http://imwheel.sourceforge.net/imwheel.1.html)
+            See [official man pages](https://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 3df0ea9e60bb0..de0a8f4d5bcd3 100644
--- a/nixos/modules/services/x11/picom.nix
+++ b/nixos/modules/services/x11/picom.nix
@@ -61,7 +61,7 @@ in {
       '';
     };
 
-    package = mkPackageOptionMD pkgs "picom" { };
+    package = mkPackageOption pkgs "picom" { };
 
     fade = mkOption {
       type = types.bool;
diff --git a/nixos/modules/services/x11/redshift.nix b/nixos/modules/services/x11/redshift.nix
index 3eb9e28edae91..80605eb11407a 100644
--- a/nixos/modules/services/x11/redshift.nix
+++ b/nixos/modules/services/x11/redshift.nix
@@ -73,14 +73,7 @@ in {
       };
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.redshift;
-      defaultText = literalExpression "pkgs.redshift";
-      description = lib.mdDoc ''
-        redshift derivation to use.
-      '';
-    };
+    package = mkPackageOption pkgs "redshift" { };
 
     executable = mkOption {
       type = types.str;
diff --git a/nixos/modules/services/x11/touchegg.nix b/nixos/modules/services/x11/touchegg.nix
index f1103c054c57f..54918245f1569 100644
--- a/nixos/modules/services/x11/touchegg.nix
+++ b/nixos/modules/services/x11/touchegg.nix
@@ -13,12 +13,7 @@ in {
   options.services.touchegg = {
     enable = mkEnableOption (lib.mdDoc "touchegg, a multi-touch gesture recognizer");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.touchegg;
-      defaultText = literalExpression "pkgs.touchegg";
-      description = lib.mdDoc "touchegg derivation to use.";
-    };
+    package = mkPackageOption pkgs "touchegg" { };
   };
 
   ###### implementation
diff --git a/nixos/modules/services/x11/unclutter-xfixes.nix b/nixos/modules/services/x11/unclutter-xfixes.nix
index 4a35176c58336..9255c8124788d 100644
--- a/nixos/modules/services/x11/unclutter-xfixes.nix
+++ b/nixos/modules/services/x11/unclutter-xfixes.nix
@@ -13,12 +13,7 @@ in {
       default = false;
     };
 
-    package = mkOption {
-      description = lib.mdDoc "unclutter-xfixes derivation to use.";
-      type = types.package;
-      default = pkgs.unclutter-xfixes;
-      defaultText = literalExpression "pkgs.unclutter-xfixes";
-    };
+    package = mkPackageOption pkgs "unclutter-xfixes" { };
 
     timeout = mkOption {
       description = lib.mdDoc "Number of seconds before the cursor is marked inactive.";
diff --git a/nixos/modules/services/x11/unclutter.nix b/nixos/modules/services/x11/unclutter.nix
index 039214a575a71..ecf7e2668cec7 100644
--- a/nixos/modules/services/x11/unclutter.nix
+++ b/nixos/modules/services/x11/unclutter.nix
@@ -13,12 +13,7 @@ in {
       default = false;
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.unclutter;
-      defaultText = literalExpression "pkgs.unclutter";
-      description = lib.mdDoc "unclutter derivation to use.";
-    };
+    package = mkPackageOption pkgs "unclutter" { };
 
     keystroke = mkOption {
       description = lib.mdDoc "Wait for a keystroke before hiding the cursor";
diff --git a/nixos/modules/services/x11/urxvtd.nix b/nixos/modules/services/x11/urxvtd.nix
index fedcb6c7293ef..bab9f43b09524 100644
--- a/nixos/modules/services/x11/urxvtd.nix
+++ b/nixos/modules/services/x11/urxvtd.nix
@@ -17,14 +17,7 @@ in {
       '';
     };
 
-    package = mkOption {
-      default = pkgs.rxvt-unicode;
-      defaultText = literalExpression "pkgs.rxvt-unicode";
-      description = lib.mdDoc ''
-        Package to install. Usually pkgs.rxvt-unicode.
-      '';
-      type = types.package;
-    };
+    package = mkPackageOption pkgs "rxvt-unicode" { };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/services/x11/window-managers/awesome.nix b/nixos/modules/services/x11/window-managers/awesome.nix
index c1231d3fbf380..0478f326825f2 100644
--- a/nixos/modules/services/x11/window-managers/awesome.nix
+++ b/nixos/modules/services/x11/window-managers/awesome.nix
@@ -30,12 +30,7 @@ in
         example = literalExpression "[ pkgs.luaPackages.vicious ]";
       };
 
-      package = mkOption {
-        default = null;
-        type = types.nullOr types.package;
-        description = lib.mdDoc "Package to use for running the Awesome WM.";
-        apply = pkg: if pkg == null then pkgs.awesome else pkg;
-      };
+      package = mkPackageOption pkgs "awesome" { };
 
       noArgb = mkOption {
         default = false;
diff --git a/nixos/modules/services/x11/window-managers/bspwm.nix b/nixos/modules/services/x11/window-managers/bspwm.nix
index c403f744cd432..cd8852cdfdee5 100644
--- a/nixos/modules/services/x11/window-managers/bspwm.nix
+++ b/nixos/modules/services/x11/window-managers/bspwm.nix
@@ -11,14 +11,8 @@ in
     services.xserver.windowManager.bspwm = {
       enable = mkEnableOption (lib.mdDoc "bspwm");
 
-      package = mkOption {
-        type        = types.package;
-        default     = pkgs.bspwm;
-        defaultText = literalExpression "pkgs.bspwm";
-        example     = literalExpression "pkgs.bspwm-unstable";
-        description = lib.mdDoc ''
-          bspwm package to use.
-        '';
+      package = mkPackageOption pkgs "bspwm" {
+        example = "bspwm-unstable";
       };
       configFile = mkOption {
         type        = with types; nullOr path;
@@ -31,14 +25,8 @@ in
       };
 
       sxhkd = {
-        package = mkOption {
-          type        = types.package;
-          default     = pkgs.sxhkd;
-          defaultText = literalExpression "pkgs.sxhkd";
-          example     = literalExpression "pkgs.sxhkd-unstable";
-          description = lib.mdDoc ''
-            sxhkd package to use.
-          '';
+        package = mkPackageOption pkgs "sxhkd" {
+          example = "sxhkd-unstable";
         };
         configFile = mkOption {
           type        = with types; nullOr path;
diff --git a/nixos/modules/services/x11/window-managers/clfswm.nix b/nixos/modules/services/x11/window-managers/clfswm.nix
index f2e4c2f91c9d5..4d47c50c87efd 100644
--- a/nixos/modules/services/x11/window-managers/clfswm.nix
+++ b/nixos/modules/services/x11/window-managers/clfswm.nix
@@ -10,14 +10,7 @@ in
   options = {
     services.xserver.windowManager.clfswm = {
       enable = mkEnableOption (lib.mdDoc "clfswm");
-      package = mkOption {
-        type        = types.package;
-        default     = pkgs.lispPackages.clfswm;
-        defaultText = literalExpression "pkgs.lispPackages.clfswm";
-        description = lib.mdDoc ''
-          clfswm package to use.
-        '';
-      };
+      package = mkPackageOption pkgs [ "lispPackages" "clfswm" ] { };
     };
   };
 
diff --git a/nixos/modules/services/x11/window-managers/dk.nix b/nixos/modules/services/x11/window-managers/dk.nix
index 152c7bc8117bf..441fc18af4b1b 100644
--- a/nixos/modules/services/x11/window-managers/dk.nix
+++ b/nixos/modules/services/x11/window-managers/dk.nix
@@ -9,7 +9,7 @@ in
     services.xserver.windowManager.dk = {
       enable = lib.mkEnableOption (lib.mdDoc "dk");
 
-      package = lib.mkPackageOptionMD pkgs "dk" { };
+      package = lib.mkPackageOption pkgs "dk" { };
     };
   };
 
diff --git a/nixos/modules/services/x11/window-managers/dwm.nix b/nixos/modules/services/x11/window-managers/dwm.nix
index 82900fd305403..b5c7d37653edf 100644
--- a/nixos/modules/services/x11/window-managers/dwm.nix
+++ b/nixos/modules/services/x11/window-managers/dwm.nix
@@ -15,11 +15,8 @@ in
   options = {
     services.xserver.windowManager.dwm = {
       enable = mkEnableOption (lib.mdDoc "dwm");
-      package = mkOption {
-        type        = types.package;
-        default     = pkgs.dwm;
-        defaultText = literalExpression "pkgs.dwm";
-        example     = literalExpression ''
+      package = mkPackageOption pkgs "dwm" {
+        example = ''
           pkgs.dwm.overrideAttrs (oldAttrs: rec {
             patches = [
               (super.fetchpatch {
@@ -29,9 +26,6 @@ in
             ];
           })
         '';
-        description = lib.mdDoc ''
-          dwm package to use.
-        '';
       };
     };
   };
diff --git a/nixos/modules/services/x11/window-managers/herbstluftwm.nix b/nixos/modules/services/x11/window-managers/herbstluftwm.nix
index 93705ada116d9..16ebc2bfe1d30 100644
--- a/nixos/modules/services/x11/window-managers/herbstluftwm.nix
+++ b/nixos/modules/services/x11/window-managers/herbstluftwm.nix
@@ -11,14 +11,7 @@ in
     services.xserver.windowManager.herbstluftwm = {
       enable = mkEnableOption (lib.mdDoc "herbstluftwm");
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.herbstluftwm;
-        defaultText = literalExpression "pkgs.herbstluftwm";
-        description = lib.mdDoc ''
-          Herbstluftwm package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "herbstluftwm" { };
 
       configFile = mkOption {
         default     = null;
diff --git a/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix
index 5bb73cd0bfb17..e824d91812a71 100644
--- a/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixos/modules/services/x11/window-managers/i3.nix
@@ -4,6 +4,10 @@ with lib;
 
 let
   cfg = config.services.xserver.windowManager.i3;
+  updateSessionEnvironmentScript = ''
+    systemctl --user import-environment PATH DISPLAY XAUTHORITY DESKTOP_SESSION XDG_CONFIG_DIRS XDG_DATA_DIRS XDG_RUNTIME_DIR XDG_SESSION_ID DBUS_SESSION_BUS_ADDRESS || true
+    dbus-update-activation-environment --systemd --all || true
+  '';
 in
 
 {
@@ -19,6 +23,15 @@ in
       '';
     };
 
+    updateSessionEnvironment = mkOption {
+      default = true;
+      type = types.bool;
+      description = lib.mdDoc ''
+        Whether to run dbus-update-activation-environment and systemctl import-environment before session start.
+        Required for xdg portals to function properly.
+      '';
+    };
+
     extraSessionCommands = mkOption {
       default     = "";
       type        = types.lines;
@@ -27,14 +40,7 @@ in
       '';
     };
 
-    package = mkOption {
-      type        = types.package;
-      default     = pkgs.i3;
-      defaultText = literalExpression "pkgs.i3";
-      description = lib.mdDoc ''
-        i3 package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "i3" { };
 
     extraPackages = mkOption {
       type = with types; listOf package;
@@ -58,6 +64,8 @@ in
       start = ''
         ${cfg.extraSessionCommands}
 
+        ${lib.optionalString cfg.updateSessionEnvironment updateSessionEnvironmentScript}
+
         ${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null)
           "-c /etc/i3/config"
         } &
diff --git a/nixos/modules/services/x11/window-managers/katriawm.nix b/nixos/modules/services/x11/window-managers/katriawm.nix
index 9a3fd5f3ca44a..106631792ff4e 100644
--- a/nixos/modules/services/x11/window-managers/katriawm.nix
+++ b/nixos/modules/services/x11/window-managers/katriawm.nix
@@ -1,7 +1,7 @@
 { config, lib, pkgs, ... }:
 
 let
-  inherit (lib) mdDoc mkEnableOption mkIf mkPackageOptionMD singleton;
+  inherit (lib) mdDoc mkEnableOption mkIf mkPackageOption singleton;
   cfg = config.services.xserver.windowManager.katriawm;
 in
 {
@@ -9,7 +9,7 @@ in
   options = {
     services.xserver.windowManager.katriawm = {
       enable = mkEnableOption (mdDoc "katriawm");
-      package = mkPackageOptionMD pkgs "katriawm" {};
+      package = mkPackageOption pkgs "katriawm" {};
     };
   };
 
diff --git a/nixos/modules/services/x11/window-managers/oroborus.nix b/nixos/modules/services/x11/window-managers/oroborus.nix
deleted file mode 100644
index 654b8708e48fe..0000000000000
--- a/nixos/modules/services/x11/window-managers/oroborus.nix
+++ /dev/null
@@ -1,25 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.xserver.windowManager.oroborus;
-in
-{
-  ###### interface
-  options = {
-    services.xserver.windowManager.oroborus.enable = mkEnableOption (lib.mdDoc "oroborus");
-  };
-
-  ###### implementation
-  config = mkIf cfg.enable {
-    services.xserver.windowManager.session = singleton {
-      name = "oroborus";
-      start = ''
-        ${pkgs.oroborus}/bin/oroborus &
-        waitPID=$!
-      '';
-    };
-    environment.systemPackages = [ pkgs.oroborus ];
-  };
-}
diff --git a/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix
index a362d5cdbeee9..1da61f5fa5e77 100644
--- a/nixos/modules/services/x11/window-managers/qtile.nix
+++ b/nixos/modules/services/x11/window-managers/qtile.nix
@@ -11,7 +11,7 @@ in
   options.services.xserver.windowManager.qtile = {
     enable = mkEnableOption (lib.mdDoc "qtile");
 
-    package = mkPackageOptionMD pkgs "qtile-unwrapped" { };
+    package = mkPackageOption pkgs "qtile-unwrapped" { };
 
     configFile = mkOption {
       type = with types; nullOr path;
diff --git a/nixos/modules/services/x11/window-managers/ragnarwm.nix b/nixos/modules/services/x11/window-managers/ragnarwm.nix
index 0843b872dba5c..7242c8b1324c4 100644
--- a/nixos/modules/services/x11/window-managers/ragnarwm.nix
+++ b/nixos/modules/services/x11/window-managers/ragnarwm.nix
@@ -11,14 +11,7 @@ in
   options = {
     services.xserver.windowManager.ragnarwm = {
       enable = mkEnableOption (lib.mdDoc "ragnarwm");
-      package = mkOption {
-        type = types.package;
-        default = pkgs.ragnarwm;
-        defaultText = literalExpression "pkgs.ragnarwm";
-        description = lib.mdDoc ''
-          The ragnar package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "ragnarwm" { };
     };
   };
 
diff --git a/nixos/modules/services/x11/xscreensaver.nix b/nixos/modules/services/x11/xscreensaver.nix
new file mode 100644
index 0000000000000..dc269b892ebcf
--- /dev/null
+++ b/nixos/modules/services/x11/xscreensaver.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.xscreensaver;
+in
+{
+  options.services.xscreensaver = {
+    enable = lib.mkEnableOption "xscreensaver user service";
+
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = pkgs.xscreensaver;
+      defaultText = lib.literalExpression "pkgs.xscreensaver";
+      description = "Which xscreensaver package to use.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # Make xscreensaver-auth setuid root so that it can (try to) prevent the OOM
+    # killer from unlocking the screen.
+    security.wrappers.xscreensaver-auth = {
+      setuid = true;
+      owner = "root";
+      group = "root";
+      source = "${pkgs.xscreensaver}/libexec/xscreensaver/xscreensaver-auth";
+    };
+
+    systemd.user.services.xscreensaver = {
+      enable = true;
+      description = "XScreenSaver";
+      after = [ "graphical-session-pre.target" ];
+      partOf = [ "graphical-session.target" ];
+      wantedBy = [ "graphical-session.target" ];
+      path = [ cfg.package ];
+      serviceConfig.ExecStart = "${cfg.package}/bin/xscreensaver -no-splash";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ vancluever AndersonTorres ];
+}
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index c2e6da4b453b6..36f25d5547cae 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -175,6 +175,31 @@ in
         "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.")
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "layout" ];
+        to = [ "services" "xserver" "xkb" "layout" ];
+      })
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "xkbModel" ];
+        to = [ "services" "xserver" "xkb" "model" ];
+      })
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "xkbOptions" ];
+        to = [ "services" "xserver" "xkb" "options" ];
+      })
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "xkbVariant" ];
+        to = [ "services" "xserver" "xkb" "variant" ];
+      })
+      (lib.mkRenamedOptionModuleWith {
+        sinceRelease = 2311;
+        from = [ "services" "xserver" "xkbDir" ];
+        to = [ "services" "xserver" "xkb" "dir" ];
+      })
     ];
 
 
@@ -339,48 +364,50 @@ in
         '';
       };
 
-      layout = mkOption {
-        type = types.str;
-        default = "us";
-        description = lib.mdDoc ''
-          Keyboard layout, or multiple keyboard layouts separated by commas.
-        '';
-      };
+      xkb = {
+        layout = mkOption {
+          type = types.str;
+          default = "us";
+          description = lib.mdDoc ''
+            X keyboard layout, or multiple keyboard layouts separated by commas.
+          '';
+        };
 
-      xkbModel = mkOption {
-        type = types.str;
-        default = "pc104";
-        example = "presario";
-        description = lib.mdDoc ''
-          Keyboard model.
-        '';
-      };
+        model = mkOption {
+          type = types.str;
+          default = "pc104";
+          example = "presario";
+          description = lib.mdDoc ''
+            X keyboard model.
+          '';
+        };
 
-      xkbOptions = mkOption {
-        type = types.commas;
-        default = "terminate:ctrl_alt_bksp";
-        example = "grp:caps_toggle,grp_led:scroll";
-        description = lib.mdDoc ''
-          X keyboard options; layout switching goes here.
-        '';
-      };
+        options = mkOption {
+          type = types.commas;
+          default = "terminate:ctrl_alt_bksp";
+          example = "grp:caps_toggle,grp_led:scroll";
+          description = lib.mdDoc ''
+            X keyboard options; layout switching goes here.
+          '';
+        };
 
-      xkbVariant = mkOption {
-        type = types.str;
-        default = "";
-        example = "colemak";
-        description = lib.mdDoc ''
-          X keyboard variant.
-        '';
-      };
+        variant = mkOption {
+          type = types.str;
+          default = "";
+          example = "colemak";
+          description = lib.mdDoc ''
+            X keyboard variant.
+          '';
+        };
 
-      xkbDir = mkOption {
-        type = types.path;
-        default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
-        defaultText = literalExpression ''"''${pkgs.xkeyboard_config}/etc/X11/xkb"'';
-        description = lib.mdDoc ''
-          Path used for -xkbdir xserver parameter.
-        '';
+        dir = mkOption {
+          type = types.path;
+          default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
+          defaultText = literalExpression ''"''${pkgs.xkeyboard_config}/etc/X11/xkb"'';
+          description = lib.mdDoc ''
+            Path used for -xkbdir xserver parameter.
+          '';
+        };
       };
 
       config = mkOption {
@@ -667,7 +694,7 @@ in
         {
           "X11/xorg.conf".source = "${configFile}";
           # -xkbdir command line option does not seems to be passed to xkbcomp.
-          "X11/xkb".source = "${cfg.xkbDir}";
+          "X11/xkb".source = "${cfg.xkb.dir}";
         })
       # localectl looks into 00-keyboard.conf
       //{
@@ -675,10 +702,10 @@ in
             Section "InputClass"
               Identifier "Keyboard catchall"
               MatchIsKeyboard "on"
-              Option "XkbModel" "${cfg.xkbModel}"
-              Option "XkbLayout" "${cfg.layout}"
-              Option "XkbOptions" "${cfg.xkbOptions}"
-              Option "XkbVariant" "${cfg.xkbVariant}"
+              Option "XkbModel" "${cfg.xkb.model}"
+              Option "XkbLayout" "${cfg.xkb.layout}"
+              Option "XkbOptions" "${cfg.xkb.options}"
+              Option "XkbVariant" "${cfg.xkb.variant}"
             EndSection
           '';
         }
@@ -759,7 +786,7 @@ in
 
     services.xserver.displayManager.xserverArgs =
       [ "-config ${configFile}"
-        "-xkbdir" "${cfg.xkbDir}"
+        "-xkbdir" "${cfg.xkb.dir}"
       ] ++ optional (cfg.display != null) ":${toString cfg.display}"
         ++ optional (cfg.tty     != null) "vt${toString cfg.tty}"
         ++ optional (cfg.dpi     != null) "-dpi ${toString cfg.dpi}"
@@ -777,14 +804,14 @@ in
       ];
 
     system.checks = singleton (pkgs.runCommand "xkb-validated" {
-      inherit (cfg) xkbModel layout xkbVariant xkbOptions;
+      inherit (cfg.xkb) dir model layout variant options;
       nativeBuildInputs = with pkgs.buildPackages; [ xkbvalidate ];
       preferLocalBuild = true;
     } ''
       ${optionalString (config.environment.sessionVariables ? XKB_CONFIG_ROOT)
         "export XKB_CONFIG_ROOT=${config.environment.sessionVariables.XKB_CONFIG_ROOT}"
       }
-      xkbvalidate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
+      XKB_CONFIG_ROOT="$dir" xkbvalidate "$model" "$layout" "$variant" "$options"
       touch "$out"
     '');
 
diff --git a/nixos/modules/system/activation/activatable-system.nix b/nixos/modules/system/activation/activatable-system.nix
index 7f6154794bd8d..3d941596747bf 100644
--- a/nixos/modules/system/activation/activatable-system.nix
+++ b/nixos/modules/system/activation/activatable-system.nix
@@ -1,52 +1,16 @@
-{ config, lib, pkgs, ... }:
+{ options, config, lib, pkgs, ... }:
 
 let
   inherit (lib)
     mkOption
-    optionalString
     types
     ;
 
-  perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
-
   systemBuilderArgs = {
     activationScript = config.system.activationScripts.script;
     dryActivationScript = config.system.dryActivationScript;
   };
 
-  systemBuilderCommands = ''
-    echo "$activationScript" > $out/activate
-    echo "$dryActivationScript" > $out/dry-activate
-    substituteInPlace $out/activate --subst-var-by out ''${!toplevelVar}
-    substituteInPlace $out/dry-activate --subst-var-by out ''${!toplevelVar}
-    chmod u+x $out/activate $out/dry-activate
-    unset activationScript dryActivationScript
-
-    mkdir $out/bin
-    substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \
-      --subst-var out \
-      --subst-var-by toplevel ''${!toplevelVar} \
-      --subst-var-by coreutils "${pkgs.coreutils}" \
-      --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \
-      --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \
-      --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \
-      --subst-var-by perl "${perlWrapped}" \
-      --subst-var-by shell "${pkgs.bash}/bin/sh" \
-      --subst-var-by su "${pkgs.shadow.su}/bin/su" \
-      --subst-var-by systemd "${config.systemd.package}" \
-      --subst-var-by utillinux "${pkgs.util-linux}" \
-      ;
-
-    chmod +x $out/bin/switch-to-configuration
-    ${optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
-      if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then
-        echo "switch-to-configuration syntax is not valid:"
-        echo "$output"
-        exit 1
-      fi
-    ''}
-  '';
-
 in
 {
   options = {
@@ -60,6 +24,18 @@ in
         do, but for image based systems, this may not be needed or not be desirable.
       '';
     };
+    system.activatableSystemBuilderCommands = options.system.systemBuilderCommands // {
+      description = lib.mdDoc ''
+        Like `system.systemBuilderCommands`, but only for the commands that are
+        needed *both* when the system is activatable and when it isn't.
+
+        Disclaimer: This option might go away in the future. It might be
+        superseded by separating switch-to-configuration into a separate script
+        which will make this option superfluous. See
+        https://github.com/NixOS/nixpkgs/pull/263462#discussion_r1373104845 for
+        a discussion.
+      '';
+    };
     system.build.separateActivationScript = mkOption {
       type = types.package;
       description = ''
@@ -71,7 +47,18 @@ in
     };
   };
   config = {
-    system.systemBuilderCommands = lib.mkIf config.system.activatable systemBuilderCommands;
+    system.activatableSystemBuilderCommands = ''
+      echo "$activationScript" > $out/activate
+      echo "$dryActivationScript" > $out/dry-activate
+      substituteInPlace $out/activate --subst-var-by out ''${!toplevelVar}
+      substituteInPlace $out/dry-activate --subst-var-by out ''${!toplevelVar}
+      chmod u+x $out/activate $out/dry-activate
+      unset activationScript dryActivationScript
+    '';
+
+    system.systemBuilderCommands = lib.mkIf
+      config.system.activatable
+      config.system.activatableSystemBuilderCommands;
     system.systemBuilderArgs = lib.mkIf config.system.activatable
       (systemBuilderArgs // {
         toplevelVar = "out";
@@ -86,7 +73,7 @@ in
         })
         ''
           mkdir $out
-          ${systemBuilderCommands}
+          ${config.system.activatableSystemBuilderCommands}
         '';
   };
 }
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index c8407dd6779a3..bc0b7266ce959 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -55,10 +55,6 @@ let
       # used as a garbage collection root.
       ln -sfn "$(readlink -f "$systemConfig")" /run/current-system
 
-      # Prevent the current configuration from being garbage-collected.
-      mkdir -p /nix/var/nix/gcroots
-      ln -sfn /run/current-system /nix/var/nix/gcroots/current-system
-
       exit $_status
     '';
 
@@ -233,23 +229,15 @@ in
   config = {
 
     system.activationScripts.stdio = ""; # obsolete
+    system.activationScripts.var = ""; # obsolete
 
-    system.activationScripts.var =
-      ''
-        # Various log/runtime directories.
-
-        mkdir -p /var/tmp
-        chmod 1777 /var/tmp
-
-        # Empty, immutable home directory of many system accounts.
-        mkdir -p /var/empty
-        # Make sure it's really empty
-        ${pkgs.e2fsprogs}/bin/chattr -f -i /var/empty || true
-        find /var/empty -mindepth 1 -delete
-        chmod 0555 /var/empty
-        chown root:root /var/empty
-        ${pkgs.e2fsprogs}/bin/chattr -f +i /var/empty || true
-      '';
+    systemd.tmpfiles.rules = [
+      # Prevent the current configuration from being garbage-collected.
+      "d /nix/var/nix/gcroots -"
+      "L+ /nix/var/nix/gcroots/current-system - - - - /run/current-system"
+      "D /var/empty 0555 root root -"
+      "h /var/empty - - - - +i"
+    ];
 
     system.activationScripts.usrbinenv = if config.environment.usrbinenv != null
       then ''
diff --git a/nixos/modules/system/activation/bootspec.nix b/nixos/modules/system/activation/bootspec.nix
index 9e1fa309d5db0..2ed6964b2a6a6 100644
--- a/nixos/modules/system/activation/bootspec.nix
+++ b/nixos/modules/system/activation/bootspec.nix
@@ -11,6 +11,7 @@
 let
   cfg = config.boot.bootspec;
   children = lib.mapAttrs (childName: childConfig: childConfig.configuration.system.build.toplevel) config.specialisation;
+  hasAtLeastOneInitrdSecret = lib.length (lib.attrNames config.boot.initrd.secrets) > 0;
   schemas = {
     v1 = rec {
       filename = "boot.json";
@@ -27,6 +28,7 @@ let
               label = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})";
             } // lib.optionalAttrs config.boot.initrd.enable {
               initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
+            } // lib.optionalAttrs hasAtLeastOneInitrdSecret {
               initrdSecrets = "${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets";
             };
           }));
@@ -79,7 +81,7 @@ in
       // { default = true; internal = true; };
     enableValidation = lib.mkEnableOption (lib.mdDoc ''the validation of bootspec documents for each build.
       This will introduce Go in the build-time closure as we are relying on [Cuelang](https://cuelang.org/) for schema validation.
-      Enable this option if you want to ascertain that your documents are correct.
+      Enable this option if you want to ascertain that your documents are correct
       ''
     );
 
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index 8bd450d7343b2..ba45231465fb4 100755
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -22,6 +22,7 @@ use JSON::PP;
 use IPC::Cmd;
 use Sys::Syslog qw(:standard :macros);
 use Cwd qw(abs_path);
+use Fcntl ':flock';
 
 ## no critic(ControlStructures::ProhibitDeepNests)
 ## no critic(ErrorHandling::RequireCarping)
@@ -74,7 +75,7 @@ if ("@localeArchive@" ne "") {
 
 if (!defined($action) || ($action ne "switch" && $action ne "boot" && $action ne "test" && $action ne "dry-activate")) {
     print STDERR <<"EOF";
-Usage: $0 [switch|boot|test]
+Usage: $0 [switch|boot|test|dry-activate]
 
 switch:       make the configuration the boot default and activate now
 boot:         make the configuration the boot default
@@ -91,6 +92,8 @@ if (!-f "/etc/NIXOS" && (read_file("/etc/os-release", err_mode => "quiet") // ""
 }
 
 make_path("/run/nixos", { mode => oct(755) });
+open(my $stc_lock, '>>', '/run/nixos/switch-to-configuration.lock') or die "Could not open lock - $!";
+flock($stc_lock, LOCK_EX) or die "Could not acquire lock - $!";
 openlog("nixos", "", LOG_USER);
 
 # Install or update the bootloader.
@@ -599,7 +602,9 @@ while (my ($unit, $state) = each(%{$active_cur})) {
                     $units_to_start{$unit} = 1;
                     record_unit($start_list_file, $unit);
                     # Don't spam the user with target units that always get started.
-                    $units_to_filter{$unit} = 1;
+                    if (($ENV{"STC_DISPLAY_ALL_UNITS"} // "") ne "1") {
+                        $units_to_filter{$unit} = 1;
+                    }
                 }
             }
 
@@ -661,10 +666,20 @@ foreach my $mount_point (keys(%{$cur_fss})) {
         # Filesystem entry disappeared, so unmount it.
         $units_to_stop{$unit} = 1;
     } elsif ($cur->{fsType} ne $new->{fsType} || $cur->{device} ne $new->{device}) {
-        # Filesystem type or device changed, so unmount and mount it.
-        $units_to_stop{$unit} = 1;
-        $units_to_start{$unit} = 1;
-        record_unit($start_list_file, $unit);
+        if ($mount_point eq '/' or $mount_point eq '/nix') {
+            if ($cur->{options} ne $new->{options}) {
+                # Mount options changed, so remount it.
+                $units_to_reload{$unit} = 1;
+                record_unit($reload_list_file, $unit);
+            } else {
+                # Don't unmount / or /nix if the device changed
+                $units_to_skip{$unit} = 1;
+            }
+        } else {
+            # Filesystem type or device changed, so unmount and mount it.
+            $units_to_restart{$unit} = 1;
+            record_unit($restart_list_file, $unit);
+        }
     } elsif ($cur->{options} ne $new->{options}) {
         # Mount options changes, so remount it.
         $units_to_reload{$unit} = 1;
@@ -874,9 +889,15 @@ while (my $f = <$list_active_users>) {
 
 close($list_active_users) || die("Unable to close the file handle to loginctl");
 
-# Set the new tmpfiles
-print STDERR "setting up tmpfiles\n";
-system("$new_systemd/bin/systemd-tmpfiles", "--create", "--remove", "--exclude-prefix=/dev") == 0 or $res = 3;
+# Restart sysinit-reactivation.target.
+# This target only exists to restart services ordered before sysinit.target. We
+# cannot use X-StopOnReconfiguration to restart sysinit.target because then ALL
+# services of the system would be restarted since all normal services have a
+# default dependency on sysinit.target. sysinit-reactivation.target ensures
+# that services ordered BEFORE sysinit.target get re-started in the correct
+# order. Ordering between these services is respected.
+print STDERR "restarting sysinit-reactivation.target\n";
+system("$new_systemd/bin/systemctl", "restart", "sysinit-reactivation.target") == 0 or $res = 4;
 
 # Before reloading we need to ensure that the units are still active. They may have been
 # deactivated because one of their requirements got stopped. If they are inactive
@@ -973,4 +994,5 @@ if ($res == 0) {
     syslog(LOG_ERR, "switching to system configuration $toplevel failed (status $res)");
 }
 
+close($stc_lock) or die "Could not close lock - $!";
 exit($res);
diff --git a/nixos/modules/system/activation/switchable-system.nix b/nixos/modules/system/activation/switchable-system.nix
new file mode 100644
index 0000000000000..00bc18e48d1fb
--- /dev/null
+++ b/nixos/modules/system/activation/switchable-system.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  perlWrapped = pkgs.perl.withPackages (p: with p; [ ConfigIniFiles FileSlurp ]);
+
+in
+
+{
+
+  options = {
+    system.switch.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Whether to include the capability to switch configurations.
+
+        Disabling this makes the system unable to be reconfigured via `nixos-rebuild`.
+
+        This is good for image based appliances where updates are handled
+        outside the image. Reducing features makes the image lighter and
+        slightly more secure.
+      '';
+    };
+  };
+
+  config = lib.mkIf config.system.switch.enable {
+    system.activatableSystemBuilderCommands = ''
+      mkdir $out/bin
+      substitute ${./switch-to-configuration.pl} $out/bin/switch-to-configuration \
+        --subst-var out \
+        --subst-var-by toplevel ''${!toplevelVar} \
+        --subst-var-by coreutils "${pkgs.coreutils}" \
+        --subst-var-by distroId ${lib.escapeShellArg config.system.nixos.distroId} \
+        --subst-var-by installBootLoader ${lib.escapeShellArg config.system.build.installBootLoader} \
+        --subst-var-by localeArchive "${config.i18n.glibcLocales}/lib/locale/locale-archive" \
+        --subst-var-by perl "${perlWrapped}" \
+        --subst-var-by shell "${pkgs.bash}/bin/sh" \
+        --subst-var-by su "${pkgs.shadow.su}/bin/su" \
+        --subst-var-by systemd "${config.systemd.package}" \
+        --subst-var-by utillinux "${pkgs.util-linux}" \
+        ;
+
+      chmod +x $out/bin/switch-to-configuration
+      ${lib.optionalString (pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) ''
+        if ! output=$(${perlWrapped}/bin/perl -c $out/bin/switch-to-configuration 2>&1); then
+          echo "switch-to-configuration syntax is not valid:"
+          echo "$output"
+          exit 1
+        fi
+      ''}
+    '';
+  };
+
+}
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index fb7afce4a5808..08e3dce708447 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -1,6 +1,6 @@
 { config, lib, pkgs, ... }:
 let
-  inherit (lib) mkOption mkDefault types optionalString stringAfter;
+  inherit (lib) mkOption mkDefault types optionalString;
 
   cfg = config.boot.binfmt;
 
@@ -20,17 +20,13 @@ let
                  optionalString fixBinary "F";
   in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
 
-  activationSnippet = name: { interpreter, wrapInterpreterInShell, ... }: if wrapInterpreterInShell then ''
-    rm -f /run/binfmt/${name}
-    cat > /run/binfmt/${name} << 'EOF'
-    #!${pkgs.bash}/bin/sh
-    exec -- ${interpreter} "$@"
-    EOF
-    chmod +x /run/binfmt/${name}
-  '' else ''
-    rm -f /run/binfmt/${name}
-    ln -s ${interpreter} /run/binfmt/${name}
-  '';
+  mkInterpreter = name: { interpreter, wrapInterpreterInShell, ... }:
+    if wrapInterpreterInShell
+    then pkgs.writeShellScript "${name}-interpreter" ''
+           #!${pkgs.bash}/bin/sh
+           exec -- ${interpreter} "$@"
+         ''
+    else interpreter;
 
   getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
   getQemuArch = system: (lib.systems.elaborate { inherit system; }).qemuArch;
@@ -279,7 +275,7 @@ in {
           support your new systems.
           Warning: the builder can execute all emulated systems within the same build, which introduces impurities in the case of cross compilation.
         '';
-        type = types.listOf types.str;
+        type = types.listOf (types.enum (builtins.attrNames magics));
       };
     };
   };
@@ -318,18 +314,25 @@ in {
 
     environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
       (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
-    system.activationScripts.binfmt = stringAfter [ "specialfs" ] ''
-      mkdir -p /run/binfmt
-      chmod 0755 /run/binfmt
-      ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
-    '';
-    systemd = lib.mkIf (config.boot.binfmt.registrations != {}) {
-      additionalUpstreamSystemUnits = [
-        "proc-sys-fs-binfmt_misc.automount"
-        "proc-sys-fs-binfmt_misc.mount"
-        "systemd-binfmt.service"
-      ];
-      services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
-    };
+
+    systemd = lib.mkMerge [
+      ({ tmpfiles.rules = [
+          "d /run/binfmt 0755 -"
+        ] ++ lib.mapAttrsToList
+          (name: interpreter:
+            "L+ /run/binfmt/${name} - - - - ${interpreter}"
+          )
+          (lib.mapAttrs mkInterpreter config.boot.binfmt.registrations);
+      })
+
+      (lib.mkIf (config.boot.binfmt.registrations != {}) {
+        additionalUpstreamSystemUnits = [
+          "proc-sys-fs-binfmt_misc.automount"
+          "proc-sys-fs-binfmt_misc.mount"
+          "systemd-binfmt.service"
+        ];
+        services.systemd-binfmt.restartTriggers = [ (builtins.toJSON config.boot.binfmt.registrations) ];
+      })
+    ];
   };
 }
diff --git a/nixos/modules/system/boot/clevis.md b/nixos/modules/system/boot/clevis.md
new file mode 100644
index 0000000000000..91eb728a919ea
--- /dev/null
+++ b/nixos/modules/system/boot/clevis.md
@@ -0,0 +1,51 @@
+# Clevis {#module-boot-clevis}
+
+[Clevis](https://github.com/latchset/clevis)
+is a framework for automated decryption of resources.
+Clevis allows for secure unattended disk decryption during boot, using decryption policies that must be satisfied for the data to decrypt.
+
+
+## Create a JWE file containing your secret {#module-boot-clevis-create-secret}
+
+The first step is to embed your secret in a [JWE](https://en.wikipedia.org/wiki/JSON_Web_Encryption) file.
+JWE files have to be created through the clevis command line. 3 types of policies are supported:
+
+1) TPM policies
+
+Secrets are pinned against the presence of a TPM2 device, for example:
+```
+echo hi | clevis encrypt tpm2 '{}' > hi.jwe
+```
+2) Tang policies
+
+Secrets are pinned against the presence of a Tang server, for example:
+```
+echo hi | clevis encrypt tang '{"url": "http://tang.local"}' > hi.jwe
+```
+
+3) Shamir Secret Sharing
+
+Using Shamir's Secret Sharing ([sss](https://en.wikipedia.org/wiki/Shamir%27s_secret_sharing)), secrets are pinned using a combination of the two preceding policies. For example:
+```
+echo hi | clevis encrypt sss \
+'{"t": 2, "pins": {"tpm2": {"pcr_ids": "0"}, "tang": {"url": "http://tang.local"}}}' \
+> hi.jwe
+```
+
+For more complete documentation on how to generate a secret with clevis, see the [clevis documentation](https://github.com/latchset/clevis).
+
+
+## Activate unattended decryption of a resource at boot {#module-boot-clevis-activate}
+
+In order to activate unattended decryption of a resource at boot, enable the `clevis` module:
+
+```
+boot.initrd.clevis.enable = true;
+```
+
+Then, specify the device you want to decrypt using a given clevis secret. Clevis will automatically try to decrypt the device at boot and will fallback to interactive unlocking if the decryption policy is not fulfilled.
+```
+boot.initrd.clevis.devices."/dev/nvme0n1p1".secretFile = ./nvme0n1p1.jwe;
+```
+
+Only `bcachefs`, `zfs` and `luks` encrypted devices are supported at this time.
diff --git a/nixos/modules/system/boot/clevis.nix b/nixos/modules/system/boot/clevis.nix
new file mode 100644
index 0000000000000..0c72590f93851
--- /dev/null
+++ b/nixos/modules/system/boot/clevis.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.boot.initrd.clevis;
+  systemd = config.boot.initrd.systemd;
+  supportedFs = [ "zfs" "bcachefs" ];
+in
+{
+  meta.maintainers = with maintainers; [ julienmalka camillemndn ];
+  meta.doc = ./clevis.md;
+
+  options = {
+    boot.initrd.clevis.enable = mkEnableOption (lib.mdDoc "Clevis in initrd");
+
+
+    boot.initrd.clevis.package = mkOption {
+      type = types.package;
+      default = pkgs.clevis;
+      defaultText = "pkgs.clevis";
+      description = lib.mdDoc "Clevis package";
+    };
+
+    boot.initrd.clevis.devices = mkOption {
+      description = "Encrypted devices that need to be unlocked at boot using Clevis";
+      default = { };
+      type = types.attrsOf (types.submodule ({
+        options.secretFile = mkOption {
+          description = lib.mdDoc "Clevis JWE file used to decrypt the device at boot, in concert with the chosen pin (one of TPM2, Tang server, or SSS).";
+          type = types.path;
+        };
+      }));
+    };
+
+    boot.initrd.clevis.useTang = mkOption {
+      description = "Whether the Clevis JWE file used to decrypt the devices uses a Tang server as a pin.";
+      default = false;
+      type = types.bool;
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    # Implementation of clevis unlocking for the supported filesystems are located directly in the respective modules.
+
+
+    assertions = (attrValues (mapAttrs
+      (device: _: {
+        assertion = (any (fs: fs.device == device && (elem fs.fsType supportedFs)) config.system.build.fileSystems) || (hasAttr device config.boot.initrd.luks.devices);
+        message = ''
+          No filesystem or LUKS device with the name ${device} is declared in your configuration.'';
+      })
+      cfg.devices));
+
+
+    warnings =
+      if cfg.useTang && !config.boot.initrd.network.enable && !config.boot.initrd.systemd.network.enable
+      then [ "In order to use a Tang pinned secret you must configure networking in initrd" ]
+      else [ ];
+
+    boot.initrd = {
+      extraUtilsCommands = mkIf (!systemd.enable) ''
+        copy_bin_and_libs ${pkgs.jose}/bin/jose
+        copy_bin_and_libs ${pkgs.curl}/bin/curl
+        copy_bin_and_libs ${pkgs.bash}/bin/bash
+
+        copy_bin_and_libs ${pkgs.tpm2-tools}/bin/.tpm2-wrapped
+        mv $out/bin/{.tpm2-wrapped,tpm2}
+        cp {${pkgs.tpm2-tss},$out}/lib/libtss2-tcti-device.so.0
+
+        copy_bin_and_libs ${cfg.package}/bin/.clevis-wrapped
+        mv $out/bin/{.clevis-wrapped,clevis}
+
+        for BIN in ${cfg.package}/bin/clevis-decrypt*; do
+          copy_bin_and_libs $BIN
+        done
+
+        for BIN in $out/bin/clevis{,-decrypt{,-null,-tang,-tpm2}}; do
+          sed -i $BIN -e 's,${pkgs.bash},,' -e 's,${pkgs.coreutils},,'
+        done
+
+        sed -i $out/bin/clevis-decrypt-tpm2 -e 's,tpm2_,tpm2 ,'
+      '';
+
+      secrets = lib.mapAttrs' (name: value: nameValuePair "/etc/clevis/${name}.jwe" value.secretFile) cfg.devices;
+
+      systemd = {
+        extraBin = mkIf systemd.enable {
+          clevis = "${cfg.package}/bin/clevis";
+          curl = "${pkgs.curl}/bin/curl";
+        };
+
+        storePaths = mkIf systemd.enable [
+          cfg.package
+          "${pkgs.jose}/bin/jose"
+          "${pkgs.curl}/bin/curl"
+          "${pkgs.tpm2-tools}/bin/tpm2_createprimary"
+          "${pkgs.tpm2-tools}/bin/tpm2_flushcontext"
+          "${pkgs.tpm2-tools}/bin/tpm2_load"
+          "${pkgs.tpm2-tools}/bin/tpm2_unseal"
+        ];
+      };
+    };
+  };
+}
diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix
index a2764187a5333..8a0fc3a03dac4 100644
--- a/nixos/modules/system/boot/grow-partition.nix
+++ b/nixos/modules/system/boot/grow-partition.nix
@@ -12,33 +12,32 @@ with lib;
   ];
 
   options = {
-    boot.growPartition = mkEnableOption (lib.mdDoc "grow the root partition on boot");
+    boot.growPartition = mkEnableOption (lib.mdDoc "growing the root partition on boot");
   };
 
   config = mkIf config.boot.growPartition {
-
-    assertions = [{
-      assertion = !config.boot.initrd.systemd.enable;
-      message = "systemd stage 1 does not support 'boot.growPartition' yet.";
-    }];
-
-    boot.initrd.extraUtilsCommands = ''
-      copy_bin_and_libs ${pkgs.gawk}/bin/gawk
-      copy_bin_and_libs ${pkgs.gnused}/bin/sed
-      copy_bin_and_libs ${pkgs.util-linux}/sbin/sfdisk
-      copy_bin_and_libs ${pkgs.util-linux}/sbin/lsblk
-
-      substitute "${pkgs.cloud-utils.guest}/bin/.growpart-wrapped" "$out/bin/growpart" \
-        --replace "${pkgs.bash}/bin/sh" "/bin/sh" \
-        --replace "awk" "gawk" \
-        --replace "sed" "gnused"
-
-      ln -s sed $out/bin/gnused
-    '';
-
-    boot.initrd.postDeviceCommands = ''
-      rootDevice="${config.fileSystems."/".device}"
-      if waitDevice "$rootDevice"; then
+    assertions = [
+      {
+        assertion = !config.boot.initrd.systemd.repart.enable && !config.systemd.repart.enable;
+        message = "systemd-repart already grows the root partition and thus you should not use boot.growPartition";
+      }
+    ];
+    systemd.services.growpart = {
+      wantedBy = [ "-.mount" ];
+      after = [ "-.mount" ];
+      before = [ "systemd-growfs-root.service" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        TimeoutSec = "infinity";
+        # growpart returns 1 if the partition is already grown
+        SuccessExitStatus = "0 1";
+      };
+
+      script = ''
+        rootDevice="${config.fileSystems."/".device}"
         rootDevice="$(readlink -f "$rootDevice")"
         parentDevice="$rootDevice"
         while [ "''${parentDevice%[0-9]}" != "''${parentDevice}" ]; do
@@ -48,11 +47,8 @@ with lib;
         if [ "''${parentDevice%[0-9]p}" != "''${parentDevice}" ] && [ -b "''${parentDevice%p}" ]; then
           parentDevice="''${parentDevice%p}"
         fi
-        TMPDIR=/run sh $(type -P growpart) "$parentDevice" "$partNum"
-        udevadm settle
-      fi
-    '';
-
+        "${pkgs.cloud-utils.guest}/bin/growpart" "$parentDevice" "$partNum"
+      '';
+    };
   };
-
 }
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
index 1d95742face34..88ba43caf0030 100644
--- a/nixos/modules/system/boot/initrd-network.nix
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -80,7 +80,7 @@ in
     };
 
     boot.initrd.network.udhcpc.enable = mkOption {
-      default = config.networking.useDHCP;
+      default = config.networking.useDHCP && !config.boot.initrd.systemd.enable;
       defaultText = "networking.useDHCP";
       type = types.bool;
       description = lib.mdDoc ''
@@ -116,11 +116,11 @@ in
 
     boot.initrd.kernelModules = [ "af_packet" ];
 
-    boot.initrd.extraUtilsCommands = ''
+    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.klibc}/lib/klibc/bin.static/ipconfig
     '';
 
-    boot.initrd.preLVMCommands = mkBefore (
+    boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (mkBefore (
       # Search for interface definitions in command line.
       ''
         ifaces=""
@@ -138,7 +138,7 @@ in
         # Bring up all interfaces.
         for iface in ${dhcpIfShellExpr}; do
           echo "bringing up network interface $iface..."
-          ip link set "$iface" up && ifaces="$ifaces $iface"
+          ip link set dev "$iface" up && ifaces="$ifaces $iface"
         done
 
         # Acquire DHCP leases.
@@ -148,12 +148,12 @@ in
         done
       ''
 
-      + cfg.postCommands);
+      + cfg.postCommands));
 
-    boot.initrd.postMountCommands = mkIf cfg.flushBeforeStage2 ''
+    boot.initrd.postMountCommands = mkIf (cfg.flushBeforeStage2 && !config.boot.initrd.systemd.enable) ''
       for iface in $ifaces; do
-        ip address flush "$iface"
-        ip link set "$iface" down
+        ip address flush dev "$iface"
+        ip link set dev "$iface" down
       done
     '';
 
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 60c5ff62ffff0..61e61f32bc5ee 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -164,13 +164,12 @@ in
           for instructions.
         '';
       }
-
-      {
-        assertion = config.boot.initrd.systemd.enable -> cfg.shell == null;
-        message = "systemd stage 1 does not support boot.initrd.network.ssh.shell";
-      }
     ];
 
+    warnings = lib.optional (config.boot.initrd.systemd.enable && cfg.shell != null) ''
+      Please set 'boot.initrd.systemd.users.root.shell' instead of 'boot.initrd.network.ssh.shell'
+    '';
+
     boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${package}/bin/sshd
       cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
@@ -235,6 +234,8 @@ in
       users.sshd = { uid = 1; group = "sshd"; };
       groups.sshd = { gid = 1; };
 
+      users.root.shell = mkIf (config.boot.initrd.network.ssh.shell != null) config.boot.initrd.network.ssh.shell;
+
       contents."/etc/ssh/authorized_keys.d/root".text =
         concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys;
       contents."/etc/ssh/sshd_config".text = sshdConfig;
@@ -242,8 +243,10 @@ in
 
       services.sshd = {
         description = "SSH Daemon";
-        wantedBy = ["initrd.target"];
-        after = ["network.target" "initrd-nixos-copy-secrets.service"];
+        wantedBy = [ "initrd.target" ];
+        after = [ "network.target" "initrd-nixos-copy-secrets.service" ];
+        before = [ "shutdown.target" ];
+        conflicts = [ "shutdown.target" ];
 
         # Keys from Nix store are world-readable, which sshd doesn't
         # like. If this were a real nix store and not the initrd, we
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index 9ea6119196761..a46331ccd431d 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -96,8 +96,8 @@ in
                                         # (required, but can be null if only config changes
                                         # are needed)
 
-          extraStructuredConfig = {     # attrset of extra configuration parameters
-            FOO = lib.kernel.yes;       # (without the CONFIG_ prefix, optional)
+          extraStructuredConfig = {     # attrset of extra configuration parameters without the CONFIG_ prefix
+            FOO = lib.kernel.yes;       # (optional)
           };                            # values should generally be lib.kernel.yes,
                                         # lib.kernel.no or lib.kernel.module
 
@@ -105,8 +105,9 @@ in
             foo = true;                 # (may be checked by other NixOS modules, optional)
           };
 
-          extraConfig = "CONFIG_FOO y"; # extra configuration options in string form
-                                        # (deprecated, use extraStructuredConfig instead, optional)
+          extraConfig = "FOO y";        # extra configuration options in string form without the CONFIG_ prefix
+                                        # (optional, multiple lines allowed to specify multiple options)
+                                        # (deprecated, use extraStructuredConfig instead)
         }
         ```
 
@@ -269,6 +270,9 @@ in
             "ata_piix"
             "pata_marvell"
 
+            # NVMe
+            "nvme"
+
             # Standard SCSI stuff.
             "sd_mod"
             "sr_mod"
diff --git a/nixos/modules/system/boot/loader/external/external.nix b/nixos/modules/system/boot/loader/external/external.nix
index 926cbd2b4b3f3..78982356a9ea8 100644
--- a/nixos/modules/system/boot/loader/external/external.nix
+++ b/nixos/modules/system/boot/loader/external/external.nix
@@ -12,7 +12,7 @@ in
   };
 
   options.boot.loader.external = {
-    enable = mkEnableOption (lib.mdDoc "use an external tool to install your bootloader");
+    enable = mkEnableOption (lib.mdDoc "using an external tool to install your bootloader");
 
     installHook = mkOption {
       type = with types; path;
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 5ef3c5cd52a80..13df609071168 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -25,7 +25,7 @@ in
           under `/boot/extlinux.conf`.  For instance,
           U-Boot's generic distro boot support uses this file format.
 
-          See [U-boot's documentation](http://git.denx.de/?p=u-boot.git;a=blob;f=doc/README.distro;hb=refs/heads/master)
+          See [U-boot's documentation](https://u-boot.readthedocs.io/en/latest/develop/distro.html)
           for more information.
         '';
       };
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 468f701ae5bc7..0556c875241a1 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -36,7 +36,7 @@ let
     # Package set of targeted architecture
     if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs;
 
-  realGrub = if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
+  realGrub = if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; zfs = cfg.zfsPackage; }
     else grubPkgs.grub2;
 
   grub =
@@ -339,7 +339,7 @@ in
           See the
           [
           GRUB source code
-          ](http://git.savannah.gnu.org/cgit/grub.git/tree/grub-core/commands/nativedisk.c?h=grub-2.04#n326)
+          ](https://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 `argv`
@@ -614,6 +614,16 @@ in
         '';
       };
 
+      zfsPackage = mkOption {
+        type = types.package;
+        internal = true;
+        default = pkgs.zfs;
+        defaultText = literalExpression "pkgs.zfs";
+        description = lib.mdDoc ''
+          Which ZFS package to use if `config.boot.loader.grub.zfsSupport` is true.
+        '';
+      };
+
       efiSupport = mkOption {
         default = false;
         type = types.bool;
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 a040518a5a575..055afe95df60b 100755..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
@@ -1,27 +1,53 @@
 #! @python3@/bin/python3 -B
 import argparse
-import shutil
-import os
-import sys
-import errno
-import subprocess
-import glob
-import tempfile
-import errno
-import warnings
 import ctypes
-libc = ctypes.CDLL("libc.so.6")
-import re
 import datetime
+import errno
 import glob
+import os
 import os.path
-from typing import NamedTuple, List, Optional
-from packaging import version
+import re
+import shutil
+import subprocess
+import sys
+import warnings
+import json
+from typing import NamedTuple, Dict, List
+from dataclasses import dataclass
+
+# These values will be replaced with actual values during the package build
+EFI_SYS_MOUNT_POINT = "@efiSysMountPoint@"
+TIMEOUT = "@timeout@"
+EDITOR = bool("@editor@")
+CONSOLE_MODE = "@consoleMode@"
+BOOTSPEC_TOOLS = "@bootspecTools@"
+DISTRO_NAME = "@distroName@"
+NIX = "@nix@"
+SYSTEMD = "@systemd@"
+CONFIGURATION_LIMIT = int("@configurationLimit@")
+CAN_TOUCH_EFI_VARIABLES = "@canTouchEfiVariables@"
+GRACEFUL = "@graceful@"
+COPY_EXTRA_FILES = "@copyExtraFiles@"
+
+@dataclass
+class BootSpec:
+    init: str
+    initrd: str
+    kernel: str
+    kernelParams: List[str]
+    label: str
+    system: str
+    toplevel: str
+    specialisations: Dict[str, "BootSpec"]
+    initrdSecrets: str | None = None
+
+
+libc = ctypes.CDLL("libc.so.6")
 
 class SystemIdentifier(NamedTuple):
-    profile: Optional[str]
+    profile: str | None
     generation: int
-    specialisation: Optional[str]
+    specialisation: str | None
 
 
 def copy_if_not_exists(source: str, dest: str) -> None:
@@ -29,13 +55,13 @@ def copy_if_not_exists(source: str, dest: str) -> None:
         shutil.copyfile(source, dest)
 
 
-def generation_dir(profile: Optional[str], generation: int) -> str:
+def generation_dir(profile: str | None, generation: int) -> str:
     if profile:
         return "/nix/var/nix/profiles/system-profiles/%s-%d-link" % (profile, generation)
     else:
         return "/nix/var/nix/profiles/system-%d-link" % (generation)
 
-def system_dir(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
+def system_dir(profile: str | None, generation: int, specialisation: str | None) -> str:
     d = generation_dir(profile, generation)
     if specialisation:
         return os.path.join(d, "specialisation", specialisation)
@@ -49,7 +75,7 @@ initrd {initrd}
 options {kernel_params}
 """
 
-def generation_conf_filename(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
+def generation_conf_filename(profile: str | None, generation: int, specialisation: str | None) -> str:
     pieces = [
         "nixos",
         profile or None,
@@ -60,66 +86,66 @@ def generation_conf_filename(profile: Optional[str], generation: int, specialisa
     return "-".join(p for p in pieces if p) + ".conf"
 
 
-def write_loader_conf(profile: Optional[str], generation: int, specialisation: Optional[str]) -> None:
-    with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
-        if "@timeout@" != "":
-            f.write("timeout @timeout@\n")
+def write_loader_conf(profile: str | None, generation: int, specialisation: str | None) -> None:
+    with open(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", 'w') as f:
+        if TIMEOUT != "":
+            f.write(f"timeout {TIMEOUT}\n")
         f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation))
-        if not @editor@:
-            f.write("editor 0\n");
-        f.write("console-mode @consoleMode@\n");
-    os.rename("@efiSysMountPoint@/loader/loader.conf.tmp", "@efiSysMountPoint@/loader/loader.conf")
-
+        if not EDITOR:
+            f.write("editor 0\n")
+        f.write(f"console-mode {CONSOLE_MODE}\n")
+        f.flush()
+        os.fsync(f.fileno())
+    os.rename(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf")
+
+
+def get_bootspec(profile: str | None, generation: int) -> BootSpec:
+    system_directory = system_dir(profile, generation, None)
+    boot_json_path = os.path.realpath("%s/%s" % (system_directory, "boot.json"))
+    if os.path.isfile(boot_json_path):
+        boot_json_f = open(boot_json_path, 'r')
+        bootspec_json = json.load(boot_json_f)
+    else:
+        boot_json_str = subprocess.check_output([
+        f"{BOOTSPEC_TOOLS}/bin/synthesize",
+        "--version",
+        "1",
+        system_directory,
+        "/dev/stdout"],
+        universal_newlines=True)
+        bootspec_json = json.loads(boot_json_str)
+    return bootspec_from_json(bootspec_json)
 
-def profile_path(profile: Optional[str], generation: int, specialisation: Optional[str], name: str) -> str:
-    return os.path.realpath("%s/%s" % (system_dir(profile, generation, specialisation), name))
+def bootspec_from_json(bootspec_json: Dict) -> BootSpec:
+    specialisations = bootspec_json['org.nixos.specialisation.v1']
+    specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()}
+    return BootSpec(**bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations)
 
 
-def copy_from_profile(profile: Optional[str], generation: int, specialisation: Optional[str], name: str, dry_run: bool = False) -> str:
-    store_file_path = profile_path(profile, generation, specialisation, name)
+def copy_from_file(file: str, dry_run: bool = False) -> str:
+    store_file_path = os.path.realpath(file)
     suffix = os.path.basename(store_file_path)
     store_dir = os.path.basename(os.path.dirname(store_file_path))
     efi_file_path = "/efi/nixos/%s-%s.efi" % (store_dir, suffix)
     if not dry_run:
-        copy_if_not_exists(store_file_path, "@efiSysMountPoint@%s" % (efi_file_path))
+        copy_if_not_exists(store_file_path, f"{EFI_SYS_MOUNT_POINT}%s" % (efi_file_path))
     return efi_file_path
 
+def write_entry(profile: str | None, generation: int, specialisation: str | None,
+                machine_id: str, bootspec: BootSpec, current: bool) -> None:
+    if specialisation:
+        bootspec = bootspec.specialisations[specialisation]
+    kernel = copy_from_file(bootspec.kernel)
+    initrd = copy_from_file(bootspec.initrd)
 
-def describe_generation(profile: Optional[str], generation: int, specialisation: Optional[str]) -> str:
-    try:
-        with open(profile_path(profile, generation, specialisation, "nixos-version")) as f:
-            nixos_version = f.read()
-    except IOError:
-        nixos_version = "Unknown"
-
-    kernel_dir = os.path.dirname(profile_path(profile, generation, specialisation, "kernel"))
-    module_dir = glob.glob("%s/lib/modules/*" % kernel_dir)[0]
-    kernel_version = os.path.basename(module_dir)
-
-    build_time = int(os.path.getctime(system_dir(profile, generation, specialisation)))
-    build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
-
-    description = "@distroName@ {}, Linux Kernel {}, Built on {}".format(
-        nixos_version, kernel_version, build_date
-    )
-
-    return description
-
-
-def write_entry(profile: Optional[str], generation: int, specialisation: Optional[str],
-                machine_id: str, current: bool) -> None:
-    kernel = copy_from_profile(profile, generation, specialisation, "kernel")
-    initrd = copy_from_profile(profile, generation, specialisation, "initrd")
-
-    title = "@distroName@{profile}{specialisation}".format(
+    title = "{name}{profile}{specialisation}".format(
+        name=DISTRO_NAME,
         profile=" [" + profile + "]" if profile else "",
         specialisation=" (%s)" % specialisation if specialisation else "")
 
     try:
-        append_initrd_secrets = profile_path(profile, generation, specialisation, "append-initrd-secrets")
-        subprocess.check_call([append_initrd_secrets, "@efiSysMountPoint@%s" % (initrd)])
-    except FileNotFoundError:
-        pass
+        if bootspec.initrdSecrets is not None:
+            subprocess.check_call([bootspec.initrdSecrets, f"{EFI_SYS_MOUNT_POINT}%s" % (initrd)])
     except subprocess.CalledProcessError:
         if current:
             print("failed to create initrd secrets!", file=sys.stderr)
@@ -129,36 +155,32 @@ def write_entry(profile: Optional[str], generation: int, specialisation: Optiona
                   f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr)
             print("note: this is normal after having removed "
                   "or renamed a file in `boot.initrd.secrets`", file=sys.stderr)
-    entry_file = "@efiSysMountPoint@/loader/entries/%s" % (
+    entry_file = f"{EFI_SYS_MOUNT_POINT}/loader/entries/%s" % (
         generation_conf_filename(profile, generation, specialisation))
     tmp_path = "%s.tmp" % (entry_file)
-    kernel_params = "init=%s " % profile_path(profile, generation, specialisation, "init")
+    kernel_params = "init=%s " % bootspec.init
+
+    kernel_params = kernel_params + " ".join(bootspec.kernelParams)
+    build_time = int(os.path.getctime(system_dir(profile, generation, specialisation)))
+    build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
 
-    with open(profile_path(profile, generation, specialisation, "kernel-params")) as params_file:
-        kernel_params = kernel_params + params_file.read()
     with open(tmp_path, 'w') as f:
         f.write(BOOT_ENTRY.format(title=title,
                     generation=generation,
                     kernel=kernel,
                     initrd=initrd,
                     kernel_params=kernel_params,
-                    description=describe_generation(profile, generation, specialisation)))
+                    description=f"{bootspec.label}, built on {build_date}"))
         if machine_id is not None:
             f.write("machine-id %s\n" % machine_id)
+        f.flush()
+        os.fsync(f.fileno())
     os.rename(tmp_path, entry_file)
 
 
-def mkdir_p(path: str) -> None:
-    try:
-        os.makedirs(path)
-    except OSError as e:
-        if e.errno != errno.EEXIST or not os.path.isdir(path):
-            raise
-
-
-def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]:
+def get_generations(profile: str | None = None) -> list[SystemIdentifier]:
     gen_list = subprocess.check_output([
-        "@nix@/bin/nix-env",
+        f"{NIX}/bin/nix-env",
         "--list-generations",
         "-p",
         "/nix/var/nix/profiles/%s" % ("system-profiles/" + profile if profile else "system"),
@@ -167,7 +189,7 @@ def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]:
     gen_lines = gen_list.split('\n')
     gen_lines.pop()
 
-    configurationLimit = @configurationLimit@
+    configurationLimit = CONFIGURATION_LIMIT
     configurations = [
         SystemIdentifier(
             profile=profile,
@@ -179,22 +201,15 @@ def get_generations(profile: Optional[str] = None) -> List[SystemIdentifier]:
     return configurations[-configurationLimit:]
 
 
-def get_specialisations(profile: Optional[str], generation: int, _: Optional[str]) -> List[SystemIdentifier]:
-    specialisations_dir = os.path.join(
-            system_dir(profile, generation, None), "specialisation")
-    if not os.path.exists(specialisations_dir):
-        return []
-    return [SystemIdentifier(profile, generation, spec) for spec in os.listdir(specialisations_dir)]
-
-
-def remove_old_entries(gens: List[SystemIdentifier]) -> None:
-    rex_profile = re.compile("^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
-    rex_generation = re.compile("^@efiSysMountPoint@/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
+def remove_old_entries(gens: list[SystemIdentifier]) -> None:
+    rex_profile = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$")
+    rex_generation = re.compile(r"^" + re.escape(EFI_SYS_MOUNT_POINT) + "/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"):
+        bootspec = get_bootspec(gen.profile, gen.generation)
+        known_paths.append(copy_from_file(bootspec.kernel, True))
+        known_paths.append(copy_from_file(bootspec.initrd, True))
+    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"):
         if rex_profile.match(path):
             prof = rex_profile.sub(r"\1", path)
         else:
@@ -205,12 +220,12 @@ def remove_old_entries(gens: List[SystemIdentifier]) -> None:
             continue
         if not (prof, gen_number, None) in gens:
             os.unlink(path)
-    for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
+    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/*"):
         if not path in known_paths and not os.path.isdir(path):
             os.unlink(path)
 
 
-def get_profiles() -> List[str]:
+def get_profiles() -> list[str]:
     if os.path.isdir("/nix/var/nix/profiles/system-profiles/"):
         return [x
             for x in os.listdir("/nix/var/nix/profiles/system-profiles/")
@@ -218,11 +233,7 @@ def get_profiles() -> List[str]:
     else:
         return []
 
-def main() -> None:
-    parser = argparse.ArgumentParser(description='Update @distroName@-related systemd-boot files')
-    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help='The default @distroName@ config to boot')
-    args = parser.parse_args()
-
+def install_bootloader(args: argparse.Namespace) -> None:
     try:
         with open("/etc/machine-id") as machine_file:
             machine_id = machine_file.readlines()[0]
@@ -232,7 +243,7 @@ def main() -> None:
         # Since systemd version 232 a machine ID is required and it might not
         # be there on newly installed systems, so let's generate one so that
         # bootctl can find it and we can also pass it to write_entry() later.
-        cmd = ["@systemd@/bin/systemd-machine-id-setup", "--print"]
+        cmd = [f"{SYSTEMD}/bin/systemd-machine-id-setup", "--print"]
         machine_id = subprocess.run(
           cmd, text=True, check=True, stdout=subprocess.PIPE
         ).stdout.rstrip()
@@ -244,22 +255,22 @@ def main() -> None:
     # flags to pass to bootctl install/update
     bootctl_flags = []
 
-    if "@canTouchEfiVariables@" != "1":
+    if CAN_TOUCH_EFI_VARIABLES != "1":
         bootctl_flags.append("--no-variables")
 
-    if "@graceful@" == "1":
+    if GRACEFUL == "1":
         bootctl_flags.append("--graceful")
 
     if os.getenv("NIXOS_INSTALL_BOOTLOADER") == "1":
         # bootctl uses fopen() with modes "wxe" and fails if the file exists.
-        if os.path.exists("@efiSysMountPoint@/loader/loader.conf"):
-            os.unlink("@efiSysMountPoint@/loader/loader.conf")
+        if os.path.exists(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"):
+            os.unlink(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf")
 
-        subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@"] + bootctl_flags + ["install"])
+        subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["install"])
     else:
         # Update bootloader to latest if needed
-        available_out = subprocess.check_output(["@systemd@/bin/bootctl", "--version"], universal_newlines=True).split()[2]
-        installed_out = subprocess.check_output(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@", "status"], universal_newlines=True)
+        available_out = subprocess.check_output([f"{SYSTEMD}/bin/bootctl", "--version"], universal_newlines=True).split()[2]
+        installed_out = subprocess.check_output([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}", "status"], universal_newlines=True)
 
         # See status_binaries() in systemd bootctl.c for code which generates this
         installed_match = re.search(r"^\W+File:.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
@@ -273,21 +284,15 @@ def main() -> None:
         if available_match is None:
             raise Exception("could not determine systemd-boot version")
 
-        installed_version = version.parse(installed_match.group(1))
-        available_version = version.parse(available_match.group(1))
+        installed_version = installed_match.group(1)
+        available_version = available_match.group(1)
 
-        # systemd 252 has a regression that leaves some machines unbootable, so we skip that update.
-        # The fix is in 252.2
-        # See https://github.com/systemd/systemd/issues/25363 and https://github.com/NixOS/nixpkgs/pull/201558#issuecomment-1348603263
         if installed_version < available_version:
-            if version.parse('252') <= available_version < version.parse('252.2'):
-                print("skipping systemd-boot update to %s because of known regression" % available_version)
-            else:
-                print("updating systemd-boot from %s to %s" % (installed_version, available_version))
-                subprocess.check_call(["@systemd@/bin/bootctl", "--esp-path=@efiSysMountPoint@"] + bootctl_flags + ["update"])
+            print("updating systemd-boot from %s to %s" % (installed_version, available_version))
+            subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["update"])
 
-    mkdir_p("@efiSysMountPoint@/efi/nixos")
-    mkdir_p("@efiSysMountPoint@/loader/entries")
+    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos", exist_ok=True)
+    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/loader/entries", exist_ok=True)
 
     gens = get_generations()
     for profile in get_profiles():
@@ -295,10 +300,11 @@ def main() -> None:
     remove_old_entries(gens)
     for gen in gens:
         try:
-            is_default = os.path.dirname(profile_path(*gen, "init")) == args.default_config
-            write_entry(*gen, machine_id, current=is_default)
-            for specialisation in get_specialisations(*gen):
-                write_entry(*specialisation, machine_id, current=is_default)
+            bootspec = get_bootspec(gen.profile, gen.generation)
+            is_default = os.path.dirname(bootspec.init) == args.default_config
+            write_entry(*gen, machine_id, bootspec, current=is_default)
+            for specialisation in bootspec.specialisations.keys():
+                write_entry(gen.profile, gen.generation, specialisation, machine_id, bootspec, current=is_default)
             if is_default:
                 write_loader_conf(*gen)
         except OSError as e:
@@ -309,9 +315,9 @@ def main() -> None:
             else:
                 raise e
 
-    for root, _, files in os.walk('@efiSysMountPoint@/efi/nixos/.extra-files', topdown=False):
-        relative_root = root.removeprefix("@efiSysMountPoint@/efi/nixos/.extra-files").removeprefix("/")
-        actual_root = os.path.join("@efiSysMountPoint@", relative_root)
+    for root, _, files in os.walk(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", topdown=False):
+        relative_root = root.removeprefix(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files").removeprefix("/")
+        actual_root = os.path.join(f"{EFI_SYS_MOUNT_POINT}", relative_root)
 
         for file in files:
             actual_file = os.path.join(actual_root, file)
@@ -324,17 +330,26 @@ def main() -> None:
             os.rmdir(actual_root)
         os.rmdir(root)
 
-    mkdir_p("@efiSysMountPoint@/efi/nixos/.extra-files")
+    os.makedirs(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/.extra-files", exist_ok=True)
+
+    subprocess.check_call(COPY_EXTRA_FILES)
+
 
-    subprocess.check_call("@copyExtraFiles@")
+def main() -> None:
+    parser = argparse.ArgumentParser(description=f"Update {DISTRO_NAME}-related systemd-boot files")
+    parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help=f"The default {DISTRO_NAME} config to boot")
+    args = parser.parse_args()
 
-    # Since fat32 provides little recovery facilities after a crash,
-    # it can leave the system in an unbootable state, when a crash/outage
-    # happens shortly after an update. To decrease the likelihood of this
-    # event sync the efi filesystem after each update.
-    rc = libc.syncfs(os.open("@efiSysMountPoint@", os.O_RDONLY))
-    if rc != 0:
-        print("could not sync @efiSysMountPoint@: {}".format(os.strerror(rc)), file=sys.stderr)
+    try:
+        install_bootloader(args)
+    finally:
+        # Since fat32 provides little recovery facilities after a crash,
+        # it can leave the system in an unbootable state, when a crash/outage
+        # happens shortly after an update. To decrease the likelihood of this
+        # event sync the efi filesystem after each update.
+        rc = libc.syncfs(os.open(f"{EFI_SYS_MOUNT_POINT}", os.O_RDONLY))
+        if rc != 0:
+            print(f"could not sync {EFI_SYS_MOUNT_POINT}: {os.strerror(rc)}", file=sys.stderr)
 
 
 if __name__ == '__main__':
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 1770f0759434d..3b140726c2d6a 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -7,17 +7,17 @@ let
 
   efi = config.boot.loader.efi;
 
-  python3 = pkgs.python3.withPackages (ps: [ ps.packaging ]);
-
   systemdBootBuilder = pkgs.substituteAll {
     src = ./systemd-boot-builder.py;
 
     isExecutable = true;
 
-    inherit python3;
+    inherit (pkgs) python3;
 
     systemd = config.systemd.package;
 
+    bootspecTools = pkgs.bootspec;
+
     nix = config.nix.package.out;
 
     timeout = optionalString (config.boot.loader.timeout != null) config.boot.loader.timeout;
@@ -52,7 +52,7 @@ let
   };
 
   checkedSystemdBootBuilder = pkgs.runCommand "systemd-boot" {
-    nativeBuildInputs = [ pkgs.mypy python3 ];
+    nativeBuildInputs = [ pkgs.mypy ];
   } ''
     install -m755 ${systemdBootBuilder} $out
     mypy \
@@ -69,6 +69,8 @@ let
   '';
 in {
 
+  meta.maintainers = with lib.maintainers; [ julienmalka ];
+
   imports =
     [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ])
     ];
@@ -79,7 +81,11 @@ in {
 
       type = types.bool;
 
-      description = lib.mdDoc "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.
+        For more information about systemd-boot:
+        https://www.freedesktop.org/wiki/Software/systemd/systemd-boot/
+      '';
     };
 
     editor = mkOption {
@@ -147,7 +153,7 @@ in {
         default = false;
         type = types.bool;
         description = lib.mdDoc ''
-          Make MemTest86+ available from the systemd-boot menu. MemTest86+ is a
+          Make Memtest86+ available from the systemd-boot menu. Memtest86+ is a
           program for testing memory.
         '';
       };
@@ -191,7 +197,7 @@ in {
       default = {};
       example = literalExpression ''
         { "memtest86.conf" = '''
-          title MemTest86+
+          title Memtest86+
           efi /efi/memtest86/memtest.efi
         '''; }
       '';
@@ -285,7 +291,7 @@ in {
     boot.loader.systemd-boot.extraEntries = mkMerge [
       (mkIf cfg.memtest86.enable {
         "${cfg.memtest86.entryFilename}" = ''
-          title  MemTest86
+          title  Memtest86+
           efi    /efi/memtest86/memtest.efi
         '';
       })
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index dc3fe163116e1..221e90b6f38fb 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -1,9 +1,11 @@
-{ config, options, lib, pkgs, ... }:
+{ config, options, lib, utils, pkgs, ... }:
 
 with lib;
 
 let
   luks = config.boot.initrd.luks;
+  clevis = config.boot.initrd.clevis;
+  systemd = config.boot.initrd.systemd;
   kernelPackages = config.boot.kernelPackages;
   defaultPrio = (mkOptionDefault {}).priority;
 
@@ -351,6 +353,12 @@ let
 
         new_response="$(ykchalresp -${toString dev.yubikey.slot} -x $new_challenge 2>/dev/null)"
 
+        if [ -z "$new_response" ]; then
+            echo "Warning: Unable to generate new challenge response, current challenge persists!"
+            umount /crypt-storage
+            return
+        fi
+
         if [ ! -z "$k_user" ]; then
             new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
         else
@@ -505,7 +513,7 @@ let
   postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
 
 
-  stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: let
+  stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatLines (lib.mapAttrsToList (n: v: let
     opts = v.crypttabExtraOpts
       ++ optional v.allowDiscards "discard"
       ++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ]
@@ -531,7 +539,7 @@ in
       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 <http://en.wikipedia.org/wiki/DMA_attack>.
+        More information is available at <https://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!
@@ -588,7 +596,7 @@ in
       '';
 
       type = with types; attrsOf (submodule (
-        { name, ... }: { options = {
+        { config, name, ... }: { options = {
 
           name = mkOption {
             visible = false;
@@ -888,6 +896,19 @@ in
             '';
           };
         };
+
+        config = mkIf (clevis.enable && (hasAttr name clevis.devices)) {
+          preOpenCommands = mkIf (!systemd.enable) ''
+            mkdir -p /clevis-${name}
+            mount -t ramfs none /clevis-${name}
+            clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted
+          '';
+          keyFile = "/clevis-${name}/decrypted";
+          fallbackToPassword = !systemd.enable;
+          postOpenCommands = mkIf (!systemd.enable) ''
+            umount /clevis-${name}
+          '';
+        };
       }));
     };
 
@@ -1075,6 +1096,35 @@ in
     boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands);
     boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands);
 
+    boot.initrd.systemd.services = let devicesWithClevis = filterAttrs (device: _: (hasAttr device clevis.devices)) luks.devices; in
+      mkIf (clevis.enable && systemd.enable) (
+        (mapAttrs'
+          (name: _: nameValuePair "cryptsetup-clevis-${name}" {
+            wantedBy = [ "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" ];
+            before = [
+              "systemd-cryptsetup@${utils.escapeSystemdPath name}.service"
+              "initrd-switch-root.target"
+              "shutdown.target"
+            ];
+            wants = [ "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target";
+            after = [ "systemd-modules-load.service" "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target";
+            script = ''
+              mkdir -p /clevis-${name}
+              mount -t ramfs none /clevis-${name}
+              umask 277
+              clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted
+            '';
+            conflicts = [ "initrd-switch-root.target" "shutdown.target" ];
+            unitConfig.DefaultDependencies = "no";
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
+              ExecStop = "${config.boot.initrd.systemd.package.util-linux}/bin/umount /clevis-${name}";
+            };
+          })
+          devicesWithClevis)
+      );
+
     environment.systemPackages = [ pkgs.cryptsetup ];
   };
 }
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 6d0afcc57fcc6..f236a4c005ad6 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -83,7 +83,7 @@ let
         (assertByteFormat "BitsPerSecond")
         (assertValueOneOf "Duplex" ["half" "full"])
         (assertValueOneOf "AutoNegotiation" boolValues)
-        (assertValueOneOf "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon" "off"])
+        (assertValuesSomeOfOr "WakeOnLan" ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon"] "off")
         (assertValueOneOf "Port" ["tp" "aui" "bnc" "mii" "fibre"])
         (assertValueOneOf "ReceiveChecksumOffload" boolValues)
         (assertValueOneOf "TransmitChecksumOffload" boolValues)
@@ -122,6 +122,16 @@ let
         (assertValueOneOf "PacketInfo" boolValues)
         (assertValueOneOf "VNetHeader" boolValues)
       ];
+
+      # See https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#%5BIPVTAP%5D%20Section%20Options
+      ipVlanVtapChecks = [
+        (assertOnlyFields [
+          "Mode"
+          "Flags"
+        ])
+        (assertValueOneOf "Mode" ["L2" "L3" "L3S" ])
+        (assertValueOneOf "Flags" ["private" "vepa" "bridge" ])
+      ];
     in {
 
       sectionNetdev = checkUnitConfig "Netdev" [
@@ -146,6 +156,7 @@ let
           "ip6gretap"
           "ipip"
           "ipvlan"
+          "ipvtap"
           "macvlan"
           "macvtap"
           "sit"
@@ -159,6 +170,7 @@ let
           "geneve"
           "l2tp"
           "macsec"
+          "wlan"
           "vrf"
           "vcan"
           "vxcan"
@@ -190,6 +202,10 @@ let
         (assertValueOneOf "ReorderHeader" boolValues)
       ];
 
+      sectionIPVLAN = checkUnitConfig "IPVLAN" ipVlanVtapChecks;
+
+      sectionIPVTAP = checkUnitConfig "IPVTAP" ipVlanVtapChecks;
+
       sectionMACVLAN = checkUnitConfig "MACVLAN" [
         (assertOnlyFields [
           "Mode"
@@ -468,6 +484,30 @@ let
         (assertMinimum "Table" 0)
       ];
 
+      sectionWLAN = checkUnitConfig "WLAN" [
+        (assertOnlyFields [
+          "PhysicalDevice"  # systemd supports both strings ("phy0") and indexes (0) here.
+          "Type"
+          "WDS"
+        ])
+        # See https://github.com/systemd/systemd/blob/main/src/basic/linux/nl80211.h#L3382
+        (assertValueOneOf "Type" [
+          "ad-hoc"
+          "station"
+          "ap"
+          "ap-vlan"
+          "wds"
+          "monitor"
+          "mesh-point"
+          "p2p-client"
+          "p2p-go"
+          "p2p-device"
+          "ocb"
+          "nan"
+        ])
+        (assertValueOneOf "WDS" boolValues)
+      ];
+
       sectionBatmanAdvanced = checkUnitConfig "BatmanAdvanced" [
         (assertOnlyFields [
           "GatewayMode"
@@ -517,17 +557,24 @@ let
         (assertValueOneOf "Unmanaged" boolValues)
         (assertInt "Group")
         (assertRange "Group" 0 2147483647)
-        (assertValueOneOf "RequiredForOnline" (boolValues ++ [
-          "missing"
-          "off"
-          "no-carrier"
-          "dormant"
-          "degraded-carrier"
-          "carrier"
-          "degraded"
-          "enslaved"
-          "routable"
-        ]))
+        (assertValueOneOf "RequiredForOnline" (boolValues ++ (
+          let
+            # https://freedesktop.org/software/systemd/man/networkctl.html#missing
+            operationalStates = [
+              "missing"
+              "off"
+              "no-carrier"
+              "dormant"
+              "degraded-carrier"
+              "carrier"
+              "degraded"
+              "enslaved"
+              "routable"
+            ];
+            operationalStateRanges = concatLists (imap0 (i: min: map (max: "${min}:${max}") (drop i operationalStates)) operationalStates);
+          in
+          operationalStates ++ operationalStateRanges
+        )))
         (assertValueOneOf "RequiredFamilyForOnline" [
           "ipv4"
           "ipv6"
@@ -550,6 +597,8 @@ let
           "DHCP"
           "DHCPServer"
           "LinkLocalAddressing"
+          "IPv6LinkLocalAddressGenerationMode"
+          "IPv6StableSecretAddress"
           "IPv4LLRoute"
           "DefaultRouteOnDevice"
           "LLMNR"
@@ -583,6 +632,7 @@ let
           "VRF"
           "VLAN"
           "IPVLAN"
+          "IPVTAP"
           "MACVLAN"
           "MACVTAP"
           "VXLAN"
@@ -600,6 +650,7 @@ let
         (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6"])
         (assertValueOneOf "DHCPServer" boolValues)
         (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "fallback" "ipv4-fallback"])
+        (assertValueOneOf "IPv6LinkLocalAddressGenerationMode" ["eui64" "none" "stable-privacy" "random"])
         (assertValueOneOf "IPv4LLRoute" boolValues)
         (assertValueOneOf "DefaultRouteOnDevice" boolValues)
         (assertValueOneOf "LLMNR" (boolValues ++ ["resolve"]))
@@ -799,6 +850,8 @@ let
           "UseAddress"
           "UseDNS"
           "UseNTP"
+          "UseHostname"
+          "UseDomains"
           "RouteMetric"
           "RapidCommit"
           "MUDURL"
@@ -813,16 +866,20 @@ let
           "DUIDRawData"
           "IAID"
           "UseDelegatedPrefix"
+          "SendRelease"
         ])
         (assertValueOneOf "UseAddress" boolValues)
         (assertValueOneOf "UseDNS" boolValues)
         (assertValueOneOf "UseNTP" boolValues)
+        (assertValueOneOf "UseHostname" boolValues)
+        (assertValueOneOf "UseDomains" (boolValues ++ ["route"]))
         (assertInt "RouteMetric")
         (assertValueOneOf "RapidCommit" boolValues)
         (assertValueOneOf "WithoutRA" ["no" "solicit" "information-request"])
         (assertRange "SendOption" 1 65536)
         (assertInt "IAID")
         (assertValueOneOf "UseDelegatedPrefix" boolValues)
+        (assertValueOneOf "SendRelease" boolValues)
       ];
 
       sectionDHCPPrefixDelegation = checkUnitConfig "DHCPPrefixDelegation" [
@@ -902,6 +959,9 @@ let
           "RelayTarget"
           "RelayAgentCircuitId"
           "RelayAgentRemoteId"
+          "BootServerAddress"
+          "BootServerName"
+          "BootFilename"
         ])
         (assertInt "PoolOffset")
         (assertMinimum "PoolOffset" 0)
@@ -945,10 +1005,12 @@ let
           "Prefix"
           "PreferredLifetimeSec"
           "ValidLifetimeSec"
+          "Assign"
           "Token"
         ])
         (assertValueOneOf "AddressAutoconfiguration" boolValues)
         (assertValueOneOf "OnLink" boolValues)
+        (assertValueOneOf "Assign" boolValues)
       ];
 
       sectionIPv6RoutePrefix = checkUnitConfig "IPv6RoutePrefix" [
@@ -977,7 +1039,7 @@ let
           "MulticastToUnicast"
           "NeighborSuppression"
           "Learning"
-          "Hairpin"
+          "HairPin"
           "Isolated"
           "UseBPDU"
           "FastLeave"
@@ -993,7 +1055,7 @@ let
         (assertValueOneOf "MulticastToUnicast" boolValues)
         (assertValueOneOf "NeighborSuppression" boolValues)
         (assertValueOneOf "Learning" boolValues)
-        (assertValueOneOf "Hairpin" boolValues)
+        (assertValueOneOf "HairPin" boolValues)
         (assertValueOneOf "Isolated" boolValues)
         (assertValueOneOf "UseBPDU" boolValues)
         (assertValueOneOf "FastLeave" boolValues)
@@ -1234,6 +1296,7 @@ let
           "FirewallMark"
           "Wash"
           "SplitGSO"
+          "AckFilter"
         ])
         (assertValueOneOf "AutoRateIngress" boolValues)
         (assertInt "OverheadBytes")
@@ -1266,6 +1329,7 @@ let
         (assertRange "FirewallMark" 1 4294967295)
         (assertValueOneOf "Wash" boolValues)
         (assertValueOneOf "SplitGSO" boolValues)
+        (assertValueOneOf "AckFilter" (boolValues ++ ["aggressive"]))
       ];
 
       sectionControlledDelay = checkUnitConfig "ControlledDelay" [
@@ -1551,7 +1615,7 @@ let
         description = lib.mdDoc ''
           Each attribute in this set specifies an option in the
           `[WireGuardPeer]` section of the unit.  See
-          {manpage}`systemd.network(5)` for details.
+          {manpage}`systemd.netdev(5)` for details.
         '';
       };
     };
@@ -1580,6 +1644,26 @@ let
       '';
     };
 
+    ipvlanConfig = mkOption {
+      default = {};
+      example = { Mode = "L2"; Flags = "private"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionIPVLAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the `[IPVLAN]` section of the unit.
+        See {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
+    ipvtapConfig = mkOption {
+      default = {};
+      example = { Mode = "L3"; Flags = "vepa"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionIPVTAP;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the `[IPVTAP]` section of the unit.
+        See {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
     macvlanConfig = mkOption {
       default = {};
       example = { Mode = "private"; };
@@ -1761,6 +1845,16 @@ let
       '';
     };
 
+    wlanConfig = mkOption {
+      default = {};
+      example = { PhysicalDevice = 0; Type = "station"; };
+      type = types.addCheck (types.attrsOf unitOption) check.netdev.sectionWLAN;
+      description = lib.mdDoc ''
+        Each attribute in this set specifies an option in the `[WLAN]` section of the unit.
+        See {manpage}`systemd.netdev(5)` for details.
+      '';
+    };
+
     batmanAdvancedConfig = mkOption {
       default = {};
       example = {
@@ -2706,9 +2800,12 @@ let
         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.
+
+          This is on by default if {option}`networking.useDHCP` is enabled.
         '';
         type = types.bool;
-        default = false;
+        defaultText = "config.networking.useDHCP";
+        default = config.networking.useDHCP;
       };
 
       ignoredInterfaces = mkOption {
@@ -2812,9 +2909,15 @@ let
 
       environment.etc."systemd/networkd.conf" = renderConfig cfg.config;
 
-      systemd.services.systemd-networkd = {
+      systemd.services.systemd-networkd = let
+        isReloadableUnitFileName = unitFileName: strings.hasSuffix ".network" unitFileName;
+        reloadableUnitFiles = attrsets.filterAttrs (k: v: isReloadableUnitFileName k) unitFiles;
+        nonReloadableUnitFiles = attrsets.filterAttrs (k: v: !isReloadableUnitFileName k) unitFiles;
+        unitFileSources = unitFiles: map (x: x.source) (attrValues unitFiles);
+      in {
         wantedBy = [ "multi-user.target" ];
-        restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [
+        reloadTriggers = unitFileSources reloadableUnitFiles;
+        restartTriggers = unitFileSources nonReloadableUnitFiles ++ [
           config.environment.etc."systemd/networkd.conf".source
         ];
         aliases = [ "dbus-org.freedesktop.network1.service" ];
@@ -2834,6 +2937,17 @@ let
     })
   ];
 
+  stage1Options = {
+    options.boot.initrd.systemd.network.networks = mkOption {
+      type = with types; attrsOf (submodule {
+        # Default in initrd is dhcp-on-stop, which is correct if flushBeforeStage2 = false
+        config = mkIf config.boot.initrd.network.flushBeforeStage2 {
+          networkConfig.KeepConfiguration = mkDefault false;
+        };
+      });
+    };
+  };
+
   stage1Config = let
     cfg = config.boot.initrd.systemd.network;
   in mkMerge [
@@ -2852,8 +2966,6 @@ let
 
     (mkIf cfg.enable {
 
-      systemd.package = mkDefault pkgs.systemdStage1Network;
-
       # For networkctl
       systemd.dbus.enable = mkDefault true;
 
@@ -2897,45 +3009,14 @@ let
       ];
       kernelModules = [ "af_packet" ];
 
-      systemd.services.nixos-flush-networkd = mkIf config.boot.initrd.network.flushBeforeStage2 {
-        description = "Flush Network Configuration";
-        wantedBy = ["initrd.target"];
-        after = ["systemd-networkd.service" "dbus.socket" "dbus.service"];
-        before = ["shutdown.target" "initrd-switch-root.target"];
-        conflicts = ["shutdown.target" "initrd-switch-root.target"];
-        unitConfig.DefaultDependencies = false;
-        serviceConfig = {
-          # This service does nothing when starting, but brings down
-          # interfaces when switching root. This is the easiest way to
-          # ensure proper ordering while stopping. See systemd.unit(5)
-          # section on Before= and After=. The important part is that
-          # we are stopped before units we need, like dbus.service,
-          # and that we are stopped before starting units like
-          # initrd-switch-root.target
-          Type = "oneshot";
-          RemainAfterExit = true;
-          ExecStart = "/bin/true";
-        };
-        # systemd-networkd doesn't bring down interfaces on its own
-        # when it exits (see: systemd-networkd(8)), so we have to do
-        # it ourselves. The networkctl command doesn't have a way to
-        # bring all interfaces down, so we have to iterate over the
-        # list and filter out unmanaged interfaces to bring them down
-        # individually.
-        preStop = ''
-          networkctl list --full --no-legend | while read _idx link _type _operational setup _; do
-            [ "$setup" = unmanaged ] && continue
-            networkctl down "$link"
-          done
-        '';
-      };
-
     })
   ];
 
 in
 
 {
+  imports = [ stage1Options ];
+
   options = {
     systemd.network = commonOptions true;
     boot.initrd.systemd.network = commonOptions "shallow";
@@ -2945,10 +3026,10 @@ in
     stage2Config
     (mkIf config.boot.initrd.systemd.enable {
       assertions = [{
-        assertion = config.boot.initrd.network.udhcpc.extraArgs == [];
+        assertion = !config.boot.initrd.network.udhcpc.enable && config.boot.initrd.network.udhcpc.extraArgs == [];
         message = ''
-          boot.initrd.network.udhcpc.extraArgs is not supported when
-          boot.initrd.systemd.enable is enabled
+          systemd stage 1 networking does not support 'boot.initrd.network.udhcpc'. Configure
+          DHCP with 'networking.*' options or with 'boot.initrd.systemd.network' options.
         '';
       }];
 
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index 4e7201833db6b..c42c88163c564 100644
--- a/nixos/modules/system/boot/resolved.nix
+++ b/nixos/modules/system/boot/resolved.nix
@@ -23,12 +23,13 @@ in
     };
 
     services.resolved.fallbackDns = mkOption {
-      default = [ ];
+      default = null;
       example = [ "8.8.8.8" "2001:4860:4860::8844" ];
-      type = types.listOf types.str;
+      type = types.nullOr (types.listOf types.str);
       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.
+        If this option is null, a compiled-in list of DNS servers is used instead.
+        Setting this option to an empty list will override the built-in list to an empty list, disabling fallback.
       '';
     };
 
@@ -66,7 +67,7 @@ in
     };
 
     services.resolved.dnssec = mkOption {
-      default = "allow-downgrade";
+      default = "false";
       example = "true";
       type = types.enum [ "true" "allow-downgrade" "false" ];
       description = lib.mdDoc ''
@@ -85,6 +86,35 @@ in
             synthesizing a DNS response that suggests DNSSEC was not
             supported.
         - `"false"`: DNS lookups are not DNSSEC validated.
+
+        At the time of September 2023, systemd upstream advise
+        to disable DNSSEC by default as the current code
+        is not robust enough to deal with "in the wild" non-compliant
+        servers, which will usually give you a broken bad experience
+        in addition of insecure.
+      '';
+    };
+
+    services.resolved.dnsovertls = mkOption {
+      default = "false";
+      example = "true";
+      type = types.enum [ "true" "opportunistic" "false" ];
+      description = lib.mdDoc ''
+        If set to
+        - `"true"`:
+            all DNS lookups will be encrypted. This requires
+            that the DNS server supports DNS-over-TLS and
+            has a valid certificate. If the hostname was specified
+            via the `address#hostname` format in {option}`services.resolved.domains`
+            then the specified hostname is used to validate its certificate.
+        - `"opportunistic"`:
+            all DNS lookups will attempt to be encrypted, but will fallback
+            to unecrypted requests if the server does not support DNS-over-TLS.
+            Note that this mode does allow for a malicious party to conduct a
+            downgrade attack by immitating the DNS server and pretending to not
+            support encryption.
+        - `"false"`:
+            all DNS lookups are done unencrypted.
       '';
     };
 
@@ -128,12 +158,13 @@ in
         [Resolve]
         ${optionalString (config.networking.nameservers != [])
           "DNS=${concatStringsSep " " config.networking.nameservers}"}
-        ${optionalString (cfg.fallbackDns != [])
+        ${optionalString (cfg.fallbackDns != null)
           "FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"}
         ${optionalString (cfg.domains != [])
           "Domains=${concatStringsSep " " cfg.domains}"}
         LLMNR=${cfg.llmnr}
         DNSSEC=${cfg.dnssec}
+        DNSOverTLS=${cfg.dnsovertls}
         ${config.services.resolved.extraConfig}
       '';
 
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index bc2fc7f7b1083..086e5d65da2f2 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -253,9 +253,6 @@ done
 @setHostId@
 
 # Load the required kernel modules.
-mkdir -p /lib
-ln -s @modulesClosure@/lib/modules /lib/modules
-ln -s @modulesClosure@/lib/firmware /lib/firmware
 echo @extraUtils@/bin/modprobe > /proc/sys/kernel/modprobe
 for i in @kernelModules@; do
     info "loading module $(basename $i)..."
@@ -498,6 +495,8 @@ if test -e /sys/power/resume -a -e /sys/power/disk; then
     fi
 fi
 
+@postResumeCommands@
+
 # If we have a path to an iso file, find the iso and link it to /dev/root
 if [ -n "$isoPath" ]; then
   mkdir -p /findiso
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 7aaa3f85bfe0e..e990aeea7a14b 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -123,7 +123,7 @@ let
         # ZFS properties such as `setuid=off` and `exec=off` (unless manually
         # duplicated in `fileSystems.*.options`, defeating "zfsutil"'s purpose).
         copy_bin_and_libs ${lib.getOutput "mount" pkgs.util-linux}/bin/mount
-        copy_bin_and_libs ${pkgs.zfs}/bin/mount.zfs
+        copy_bin_and_libs ${config.boot.zfs.package}/bin/mount.zfs
       ''}
 
       # Copy some util-linux stuff.
@@ -284,7 +284,7 @@ let
       # in the NixOS installation CD, so use ID_CDROM_MEDIA in the
       # corresponding udev rules for now.  This was the behaviour in
       # udev <= 154.  See also
-      #   http://www.spinics.net/lists/hotplug/msg03935.html
+      #   https://www.spinics.net/lists/hotplug/msg03935.html
       substituteInPlace $out/60-persistent-storage.rules \
         --replace ID_CDROM_MEDIA_TRACK_COUNT_DATA ID_CDROM_MEDIA
     ''; # */
@@ -307,7 +307,7 @@ let
       ${pkgs.buildPackages.busybox}/bin/ash -n $target
     '';
 
-    inherit linkUnits udevRules extraUtils modulesClosure;
+    inherit linkUnits udevRules extraUtils;
 
     inherit (config.boot) resumeDevice;
 
@@ -316,7 +316,7 @@ let
     inherit (config.system.build) earlyMountScript;
 
     inherit (config.boot.initrd) checkJournalingFS verbose
-      preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
+      preLVMCommands preDeviceCommands postDeviceCommands postResumeCommands postMountCommands preFailCommands kernelModules;
 
     resumeDevices = map (sd: if sd ? device then sd.device else "/dev/disk/by-label/${sd.label}")
                     (filter (sd: hasPrefix "/dev/" sd.device && !sd.randomEncryption.enable
@@ -349,6 +349,9 @@ let
       [ { object = bootStage1;
           symlink = "/init";
         }
+        { object = "${modulesClosure}/lib";
+          symlink = "/lib";
+        }
         { object = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" {
               src = "${pkgs.kmod-blacklist-ubuntu}/modprobe.conf";
               preferLocalBuild = true;
@@ -432,7 +435,7 @@ let
          }
 
         # mindepth 1 so that we don't change the mode of /
-        (cd "$tmp" && find . -mindepth 1 -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
+        (cd "$tmp" && find . -mindepth 1 | xargs touch -amt 197001010000 && find . -mindepth 1 -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
           ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
       '';
 
@@ -524,6 +527,14 @@ in
       '';
     };
 
+    boot.initrd.postResumeCommands = mkOption {
+      default = "";
+      type = types.lines;
+      description = lib.mdDoc ''
+        Shell commands to be executed immediately after attempting to resume.
+      '';
+    };
+
     boot.initrd.postMountCommands = mkOption {
       default = "";
       type = types.lines;
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index c0af29e3b9909..a89e3d8176374 100755
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -54,7 +54,7 @@ if [ ! -e /proc/1 ]; then
 fi
 
 
-if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ]; then
+if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" = true ] || [ ! -c /dev/kmsg ] ; then
     echo "booting system configuration ${systemConfig}"
 else
     echo "booting system configuration $systemConfig" > /dev/kmsg
@@ -105,7 +105,7 @@ fi
 
 # Required by the activation script
 install -m 0755 -d /etc
-if [ -d "/etc/nixos" ]; then
+if [ ! -h "/etc/nixos" ]; then
     install -m 0755 -d /etc/nixos
 fi
 install -m 01777 -d /tmp
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index ac55461107fb9..331ca5103ba61 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -48,6 +48,7 @@ let
       "rescue.service"
 
       # Udev.
+      "systemd-tmpfiles-setup-dev-early.service"
       "systemd-udevd-control.socket"
       "systemd-udevd-kernel.socket"
       "systemd-udevd.service"
@@ -176,189 +177,190 @@ in
 {
   ###### interface
 
-  options = {
+  options.systemd = {
 
-    systemd.package = mkOption {
-      default = pkgs.systemd;
-      defaultText = literalExpression "pkgs.systemd";
-      type = types.package;
-      description = lib.mdDoc "The systemd package.";
-    };
+    package = mkPackageOption pkgs "systemd" {};
 
-    systemd.units = mkOption {
-      description = lib.mdDoc "Definition of systemd units.";
+    units = mkOption {
+      description = "Definition of systemd units; see {manpage}`systemd.unit(5)`.";
       default = {};
       type = systemdUtils.types.units;
     };
 
-    systemd.packages = mkOption {
+    packages = mkOption {
       default = [];
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
-      description = lib.mdDoc "Packages providing systemd units and hooks.";
+      description = "Packages providing systemd units and hooks.";
     };
 
-    systemd.targets = mkOption {
+    targets = mkOption {
       default = {};
       type = systemdUtils.types.targets;
-      description = lib.mdDoc "Definition of systemd target units.";
+      description = "Definition of systemd target units; see {manpage}`systemd.target(5)`";
     };
 
-    systemd.services = mkOption {
+    services = mkOption {
       default = {};
       type = systemdUtils.types.services;
-      description = lib.mdDoc "Definition of systemd service units.";
+      description = "Definition of systemd service units; see {manpage}`systemd.service(5)`.";
     };
 
-    systemd.sockets = mkOption {
+    sockets = mkOption {
       default = {};
       type = systemdUtils.types.sockets;
-      description = lib.mdDoc "Definition of systemd socket units.";
+      description = "Definition of systemd socket units; see {manpage}`systemd.socket(5)`.";
     };
 
-    systemd.timers = mkOption {
+    timers = mkOption {
       default = {};
       type = systemdUtils.types.timers;
-      description = lib.mdDoc "Definition of systemd timer units.";
+      description = "Definition of systemd timer units; see {manpage}`systemd.timer(5)`.";
     };
 
-    systemd.paths = mkOption {
+    paths = mkOption {
       default = {};
       type = systemdUtils.types.paths;
-      description = lib.mdDoc "Definition of systemd path units.";
+      description = "Definition of systemd path units; see {manpage}`systemd.path(5)`.";
     };
 
-    systemd.mounts = mkOption {
+    mounts = mkOption {
       default = [];
       type = systemdUtils.types.mounts;
-      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.
+      description = ''
+        Definition of systemd mount units; see {manpage}`systemd.mount(5)`.
+
+        This is a list instead of an attrSet, because systemd mandates
+        the names to be derived from the `where` attribute.
       '';
     };
 
-    systemd.automounts = mkOption {
+    automounts = mkOption {
       default = [];
       type = systemdUtils.types.automounts;
-      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.
+      description = ''
+        Definition of systemd automount units; see {manpage}`systemd.automount(5)`.
+
+        This is a list instead of an attrSet, because systemd mandates
+        the names to be derived from the `where` attribute.
       '';
     };
 
-    systemd.slices = mkOption {
+    slices = mkOption {
       default = {};
       type = systemdUtils.types.slices;
-      description = lib.mdDoc "Definition of slice configurations.";
+      description = "Definition of slice configurations; see {manpage}`systemd.slice(5)`.";
     };
 
-    systemd.generators = mkOption {
+    generators = mkOption {
       type = types.attrsOf types.path;
       default = {};
       example = { systemd-gpt-auto-generator = "/dev/null"; };
-      description = lib.mdDoc ''
-        Definition of systemd generators.
+      description = ''
+        Definition of systemd generators; see {manpage}`systemd.generator(5)`.
+
         For each `NAME = VALUE` pair of the attrSet, a link is generated from
         `/etc/systemd/system-generators/NAME` to `VALUE`.
       '';
     };
 
-    systemd.shutdown = mkOption {
+    shutdown = mkOption {
       type = types.attrsOf types.path;
       default = {};
-      description = lib.mdDoc ''
+      description = ''
         Definition of systemd shutdown executables.
         For each `NAME = VALUE` pair of the attrSet, a link is generated from
         `/etc/systemd/system-shutdown/NAME` to `VALUE`.
       '';
     };
 
-    systemd.defaultUnit = mkOption {
+    defaultUnit = mkOption {
       default = "multi-user.target";
       type = types.str;
-      description = lib.mdDoc "Default unit started when the system boots.";
+      description = ''
+        Default unit started when the system boots; see {manpage}`systemd.special(7)`.
+      '';
     };
 
-    systemd.ctrlAltDelUnit = mkOption {
+    ctrlAltDelUnit = mkOption {
       default = "reboot.target";
       type = types.str;
       example = "poweroff.target";
-      description = lib.mdDoc ''
-        Target that should be started when Ctrl-Alt-Delete is pressed.
+      description = ''
+        Target that should be started when Ctrl-Alt-Delete is pressed;
+        see {manpage}`systemd.special(7)`.
       '';
     };
 
-    systemd.globalEnvironment = mkOption {
+    globalEnvironment = mkOption {
       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { TZ = "CET"; };
-      description = lib.mdDoc ''
+      description = ''
         Environment variables passed to *all* systemd units.
       '';
     };
 
-    systemd.managerEnvironment = mkOption {
+    managerEnvironment = mkOption {
       type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { SYSTEMD_LOG_LEVEL = "debug"; };
-      description = lib.mdDoc ''
+      description = ''
         Environment variables of PID 1. These variables are
         *not* passed to started units.
       '';
     };
 
-    systemd.enableCgroupAccounting = mkOption {
+    enableCgroupAccounting = mkOption {
       default = true;
       type = types.bool;
-      description = lib.mdDoc ''
-        Whether to enable cgroup accounting.
+      description = ''
+        Whether to enable cgroup accounting; see {manpage}`cgroups(7)`.
       '';
     };
 
-    systemd.enableUnifiedCgroupHierarchy = mkOption {
+    enableUnifiedCgroupHierarchy = mkOption {
       default = true;
       type = types.bool;
-      description = lib.mdDoc ''
-        Whether to enable the unified cgroup hierarchy (cgroupsv2).
+      description = ''
+        Whether to enable the unified cgroup hierarchy (cgroupsv2); see {manpage}`cgroups(7)`.
       '';
     };
 
-    systemd.extraConfig = mkOption {
+    extraConfig = mkOption {
       default = "";
       type = types.lines;
       example = "DefaultLimitCORE=infinity";
-      description = lib.mdDoc ''
-        Extra config options for systemd. See systemd-system.conf(5) man page
+      description = ''
+        Extra config options for systemd. See {manpage}`systemd-system.conf(5)` man page
         for available options.
       '';
     };
 
-    systemd.sleep.extraConfig = mkOption {
+    sleep.extraConfig = mkOption {
       default = "";
       type = types.lines;
       example = "HibernateDelaySec=1h";
-      description = lib.mdDoc ''
+      description = ''
         Extra config options for systemd sleep state logic.
-        See sleep.conf.d(5) man page for available options.
+        See {manpage}`sleep.conf.d(5)` man page for available options.
       '';
     };
 
-    systemd.additionalUpstreamSystemUnits = mkOption {
+    additionalUpstreamSystemUnits = mkOption {
       default = [ ];
       type = types.listOf types.str;
       example = [ "debug-shell.service" "systemd-quotacheck.service" ];
-      description = lib.mdDoc ''
+      description = ''
         Additional units shipped with systemd that shall be enabled.
       '';
     };
 
-    systemd.suppressedSystemUnits = mkOption {
+    suppressedSystemUnits = mkOption {
       default = [ ];
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
-      description = lib.mdDoc ''
+      description = ''
         A list of units to skip when generating system systemd configuration directory. This has
         priority over upstream units, {option}`systemd.units`, and
         {option}`systemd.additionalUpstreamSystemUnits`. The main purpose of this is to
@@ -367,47 +369,56 @@ in
       '';
     };
 
-    systemd.watchdog.device = mkOption {
+    watchdog.device = mkOption {
       type = types.nullOr types.path;
       default = null;
       example = "/dev/watchdog";
-      description = lib.mdDoc ''
+      description = ''
         The path to a hardware watchdog device which will be managed by systemd.
-        If not specified, systemd will default to /dev/watchdog.
+        If not specified, systemd will default to `/dev/watchdog`.
       '';
     };
 
-    systemd.watchdog.runtimeTime = mkOption {
+    watchdog.runtimeTime = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "30s";
-      description = lib.mdDoc ''
+      description = ''
         The amount of time which can elapse before a watchdog hardware device
-        will automatically reboot the system. Valid time units include "ms",
-        "s", "min", "h", "d", and "w".
+        will automatically reboot the system.
+
+        Valid time units include "ms", "s", "min", "h", "d", and "w";
+        see {manpage}`systemd.time(7)`.
       '';
     };
 
-    systemd.watchdog.rebootTime = mkOption {
+    watchdog.rebootTime = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "10m";
-      description = lib.mdDoc ''
+      description = ''
         The amount of time which can elapse after a reboot has been triggered
         before a watchdog hardware device will automatically reboot the system.
-        Valid time units include "ms", "s", "min", "h", "d", and "w".
+        If left `null`, systemd will use its default of 10 minutes;
+        see {manpage}`systemd-system.conf(5)`.
+
+        Valid time units include "ms", "s", "min", "h", "d", and "w";
+        see also {manpage}`systemd.time(7)`.
       '';
     };
 
-    systemd.watchdog.kexecTime = mkOption {
+    watchdog.kexecTime = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "10m";
-      description = lib.mdDoc ''
-        The amount of time which can elapse when kexec is being executed before
+      description = ''
+        The amount of time which can elapse when `kexec` is being executed before
         a watchdog hardware device will automatically reboot the system. This
-        option should only be enabled if reloadTime is also enabled. Valid
-        time units include "ms", "s", "min", "h", "d", and "w".
+        option should only be enabled if `reloadTime` is also enabled;
+        see {manpage}`kexec(8)`.
+
+        Valid time units include "ms", "s", "min", "h", "d", and "w";
+        see also {manpage}`systemd.time(7)`.
       '';
     };
   };
@@ -440,6 +451,38 @@ in
         cfg.services
     );
 
+    assertions = let
+      mkOneAssert = typeStr: name: def: {
+        assertion = lib.elem "network-online.target" def.after -> lib.elem "network-online.target" (def.wants ++ def.requires ++ def.bindsTo);
+        message = "${name}.${typeStr} is ordered after 'network-online.target' but doesn't depend on it";
+      };
+      mkAsserts = typeStr: lib.mapAttrsToList (mkOneAssert typeStr);
+      mkMountAsserts = typeStr: map (m: mkOneAssert typeStr m.what m);
+    in mkMerge [
+      (concatLists (
+        mapAttrsToList
+          (name: service:
+            map (message: {
+              assertion = false;
+              inherit message;
+            }) (concatLists [
+              (optional ((builtins.elem "network-interfaces.target" service.after) || (builtins.elem "network-interfaces.target" service.wants))
+                "Service '${name}.service' is using the deprecated target network-interfaces.target, which no longer exists. Using network.target is recommended instead."
+              )
+            ])
+          )
+          cfg.services
+      ))
+      (mkAsserts "target" cfg.targets)
+      (mkAsserts "service" cfg.services)
+      (mkAsserts "socket" cfg.sockets)
+      (mkAsserts "timer" cfg.timers)
+      (mkAsserts "path" cfg.paths)
+      (mkMountAsserts "mount" cfg.mounts)
+      (mkMountAsserts "automount" cfg.automounts)
+      (mkAsserts "slice" cfg.slices)
+    ];
+
     system.build.units = cfg.units;
 
     system.nssModules = [ cfg.package.out ];
@@ -490,32 +533,32 @@ in
       "systemd/system.conf".text = ''
         [Manager]
         ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
-        ${optionalString config.systemd.enableCgroupAccounting ''
+        ${optionalString cfg.enableCgroupAccounting ''
           DefaultCPUAccounting=yes
           DefaultIOAccounting=yes
           DefaultBlockIOAccounting=yes
           DefaultIPAccounting=yes
         ''}
         DefaultLimitCORE=infinity
-        ${optionalString (config.systemd.watchdog.device != null) ''
-          WatchdogDevice=${config.systemd.watchdog.device}
+        ${optionalString (cfg.watchdog.device != null) ''
+          WatchdogDevice=${cfg.watchdog.device}
         ''}
-        ${optionalString (config.systemd.watchdog.runtimeTime != null) ''
-          RuntimeWatchdogSec=${config.systemd.watchdog.runtimeTime}
+        ${optionalString (cfg.watchdog.runtimeTime != null) ''
+          RuntimeWatchdogSec=${cfg.watchdog.runtimeTime}
         ''}
-        ${optionalString (config.systemd.watchdog.rebootTime != null) ''
-          RebootWatchdogSec=${config.systemd.watchdog.rebootTime}
+        ${optionalString (cfg.watchdog.rebootTime != null) ''
+          RebootWatchdogSec=${cfg.watchdog.rebootTime}
         ''}
-        ${optionalString (config.systemd.watchdog.kexecTime != null) ''
-          KExecWatchdogSec=${config.systemd.watchdog.kexecTime}
+        ${optionalString (cfg.watchdog.kexecTime != null) ''
+          KExecWatchdogSec=${cfg.watchdog.kexecTime}
         ''}
 
-        ${config.systemd.extraConfig}
+        ${cfg.extraConfig}
       '';
 
       "systemd/sleep.conf".text = ''
         [Sleep]
-        ${config.systemd.sleep.extraConfig}
+        ${cfg.sleep.extraConfig}
       '';
 
       "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
@@ -543,6 +586,13 @@ in
         unitConfig.X-StopOnReconfiguration = true;
       };
 
+    # This target only exists so that services ordered before sysinit.target
+    # are restarted in the correct order, notably BEFORE the other services,
+    # when switching configurations.
+    systemd.targets.sysinit-reactivation = {
+      description = "Reactivate sysinit units";
+    };
+
     systemd.units =
          mapAttrs' (n: v: nameValuePair "${n}.path"    (pathToUnit    n v)) cfg.paths
       // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
@@ -572,7 +622,7 @@ in
     system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
       [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
         "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC"
-        "CRYPTO_SHA256" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL"
+        "CRYPTO_SHA256" "DMIID" "AUTOFS_FS" "TMPFS_POSIX_ACL"
         "TMPFS_XATTR" "SECCOMP"
       ];
 
@@ -608,7 +658,6 @@ in
     systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild
     systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
     systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
-    systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
     systemd.services.systemd-importd.environment = proxy_env;
     systemd.services.systemd-pstore.wantedBy = [ "sysinit.target" ]; # see #81138
 
diff --git a/nixos/modules/system/boot/systemd/homed.nix b/nixos/modules/system/boot/systemd/homed.nix
index 403d1690124db..b216820c0c0cd 100644
--- a/nixos/modules/system/boot/systemd/homed.nix
+++ b/nixos/modules/system/boot/systemd/homed.nix
@@ -5,7 +5,7 @@ let
 in
 {
   options.services.homed.enable = lib.mkEnableOption (lib.mdDoc ''
-    Enable systemd home area/user account manager
+    systemd home area/user account manager
   '');
 
   config = lib.mkIf cfg.enable {
diff --git a/nixos/modules/system/boot/systemd/initrd-secrets.nix b/nixos/modules/system/boot/systemd/initrd-secrets.nix
index 7b59c0cbe7b84..d375238aa146e 100644
--- a/nixos/modules/system/boot/systemd/initrd-secrets.nix
+++ b/nixos/modules/system/boot/systemd/initrd-secrets.nix
@@ -11,7 +11,8 @@
       description = "Copy secrets into place";
       # Run as early as possible
       wantedBy = [ "sysinit.target" ];
-      before = [ "cryptsetup-pre.target" ];
+      before = [ "cryptsetup-pre.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
       unitConfig.DefaultDependencies = false;
 
       # We write the secrets to /.initrd-secrets and move them because this allows
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index 3f40a5b2dfa0c..4ae07944afc3c 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -57,7 +57,6 @@ let
     "systemd-ask-password-console.service"
     "systemd-fsck@.service"
     "systemd-halt.service"
-    "systemd-hibernate-resume@.service"
     "systemd-journald-audit.socket"
     "systemd-journald-dev-log.socket"
     "systemd-journald.service"
@@ -91,8 +90,6 @@ let
 
   fileSystems = filter utils.fsNeededForBoot config.system.build.fileSystems;
 
-  needMakefs = lib.any (fs: fs.autoFormat) fileSystems;
-
   kernel-name = config.boot.kernelPackages.kernel.name or "kernel";
   modulesTree = config.system.modulesTree.override { name = kernel-name + "-modules"; };
   firmware = config.hardware.firmware;
@@ -129,15 +126,16 @@ in {
         stage 2 counterparts such as {option}`systemd.services`,
         except that `restartTriggers` and `reloadTriggers` are not
         supported.
-
-        Note: This is experimental. Some of the `boot.initrd` options
-        are not supported when this is enabled, and the options under
-        `boot.initrd.systemd` are subject to change.
       '';
     };
 
-    package = mkPackageOptionMD pkgs "systemd" {
-      default = "systemdStage1";
+    package = lib.mkOption {
+      type = lib.types.package;
+      default = config.systemd.package;
+      defaultText = lib.literalExpression "config.systemd.package";
+      description = ''
+        The systemd package to use.
+      '';
     };
 
     extraConfig = mkOption {
@@ -333,20 +331,50 @@ in {
       visible = "shallow";
       description = lib.mdDoc "Definition of slice configurations.";
     };
+
+    enableTpm2 = mkOption {
+      default = true;
+      type = types.bool;
+      description = lib.mdDoc ''
+        Whether to enable TPM2 support in the initrd.
+      '';
+    };
   };
 
   config = mkIf (config.boot.initrd.enable && cfg.enable) {
+    assertions = map (name: {
+      assertion = lib.attrByPath name (throw "impossible") config.boot.initrd == "";
+      message = ''
+        systemd stage 1 does not support 'boot.initrd.${lib.concatStringsSep "." name}'. Please
+          convert it to analogous systemd units in 'boot.initrd.systemd'.
+
+            Definitions:
+        ${lib.concatMapStringsSep "\n" ({ file, ... }: "    - ${file}") (lib.attrByPath name (throw "impossible") options.boot.initrd).definitionsWithLocations}
+      '';
+    }) [
+      [ "preFailCommands" ]
+      [ "preDeviceCommands" ]
+      [ "preLVMCommands" ]
+      [ "postDeviceCommands" ]
+      [ "postResumeCommands" ]
+      [ "postMountCommands" ]
+      [ "extraUdevRulesCommands" ]
+      [ "extraUtilsCommands" ]
+      [ "extraUtilsCommandsTest" ]
+      [ "network" "postCommands" ]
+    ];
+
     system.build = { inherit initialRamdisk; };
 
     boot.initrd.availableKernelModules = [
       # systemd needs this for some features
-      "autofs4"
+      "autofs"
       # systemd-cryptenroll
-      "tpm-tis"
-    ] ++ lib.optional (pkgs.stdenv.hostPlatform.system != "riscv64-linux") "tpm-crb";
+    ] ++ lib.optional cfg.enableTpm2 "tpm-tis"
+    ++ lib.optional (cfg.enableTpm2 && !(pkgs.stdenv.hostPlatform.isRiscV64 || pkgs.stdenv.hostPlatform.isArmv7)) "tpm-crb";
 
     boot.initrd.systemd = {
-      initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package] ++ config.system.fsPackages;
+      initrdBin = [pkgs.bash pkgs.coreutils cfg.package.kmod cfg.package];
       extraBin = {
         less = "${pkgs.less}/bin/less";
         mount = "${cfg.package.util-linux}/bin/mount";
@@ -368,8 +396,7 @@ in {
           ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
         '';
 
-        "/lib/modules".source = "${modulesClosure}/lib/modules";
-        "/lib/firmware".source = "${modulesClosure}/lib/firmware";
+        "/lib".source = "${modulesClosure}/lib";
 
         "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules;
 
@@ -400,7 +427,7 @@ in {
         "${cfg.package}/lib/systemd/systemd-fsck"
         "${cfg.package}/lib/systemd/systemd-hibernate-resume"
         "${cfg.package}/lib/systemd/systemd-journald"
-        (lib.mkIf needMakefs "${cfg.package}/lib/systemd/systemd-makefs")
+        "${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"
@@ -421,11 +448,11 @@ in {
 
         # so NSS can look up usernames
         "${pkgs.glibc}/lib/libnss_files.so.2"
-      ] ++ optionals cfg.package.withCryptsetup [
+      ] ++ optionals (cfg.package.withCryptsetup && cfg.enableTpm2) [
         # tpm2 support
         "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so"
         pkgs.tpm2-tss
-
+      ] ++ optionals cfg.package.withCryptsetup [
         # fido2 support
         "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so"
         "${pkgs.libfido2}/lib/libfido2.so.1"
diff --git a/nixos/modules/system/boot/systemd/journald-gateway.nix b/nixos/modules/system/boot/systemd/journald-gateway.nix
new file mode 100644
index 0000000000000..854965282344d
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/journald-gateway.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.journald.gateway;
+
+  cliArgs = lib.cli.toGNUCommandLineShell { } {
+    # If either of these are null / false, they are not passed in the command-line
+    inherit (cfg) cert key trust system user merge;
+  };
+in
+{
+  meta.maintainers = [ lib.maintainers.raitobezarius ];
+  options.services.journald.gateway = {
+    enable = lib.mkEnableOption "the HTTP gateway to the journal";
+
+    port = lib.mkOption {
+      default = 19531;
+      type = lib.types.port;
+      description = ''
+        The port to listen to.
+      '';
+    };
+
+    cert = lib.mkOption {
+      default = null;
+      type = with lib.types; nullOr str;
+      description = lib.mdDoc ''
+        The path to a file or `AF_UNIX` stream socket to read the server
+        certificate from.
+
+        The certificate must be in PEM format. This option switches
+        `systemd-journal-gatewayd` into HTTPS mode and must be used together
+        with {option}`services.journald.gateway.key`.
+      '';
+    };
+
+    key = lib.mkOption {
+      default = null;
+      type = with lib.types; nullOr str;
+      description = lib.mdDoc ''
+        Specify the path to a file or `AF_UNIX` stream socket to read the
+        secret server key corresponding to the certificate specified with
+        {option}`services.journald.gateway.cert` from.
+
+        The key must be in PEM format.
+
+        This key should not be world-readable, and must be readably by the
+        `systemd-journal-gateway` user.
+      '';
+    };
+
+    trust = lib.mkOption {
+      default = null;
+      type = with lib.types; nullOr str;
+      description = lib.mdDoc ''
+        Specify the path to a file or `AF_UNIX` stream socket to read a CA
+        certificate from.
+
+        The certificate must be in PEM format.
+
+        Setting this option enforces client certificate checking.
+      '';
+    };
+
+    system = lib.mkOption {
+      default = true;
+      type = lib.types.bool;
+      description = lib.mdDoc ''
+        Serve entries from system services and the kernel.
+
+        This has the same meaning as `--system` for {manpage}`journalctl(1)`.
+      '';
+    };
+
+    user = lib.mkOption {
+      default = true;
+      type = lib.types.bool;
+      description = lib.mdDoc ''
+        Serve entries from services for the current user.
+
+        This has the same meaning as `--user` for {manpage}`journalctl(1)`.
+      '';
+    };
+
+    merge = lib.mkOption {
+      default = false;
+      type = lib.types.bool;
+      description = lib.mdDoc ''
+        Serve entries interleaved from all available journals, including other
+        machines.
+
+        This has the same meaning as `--merge` option for
+        {manpage}`journalctl(1)`.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        # This prevents the weird case were disabling "system" and "user"
+        # actually enables both because the cli flags are not present.
+        assertion = cfg.system || cfg.user;
+        message = ''
+          systemd-journal-gatewayd cannot serve neither "system" nor "user"
+          journals.
+        '';
+      }
+    ];
+
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-journal-gatewayd.socket"
+      "systemd-journal-gatewayd.service"
+    ];
+
+    users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
+    users.users.systemd-journal-gateway.group = "systemd-journal-gateway";
+    users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
+
+    systemd.services.systemd-journal-gatewayd.serviceConfig.ExecStart = [
+        # Clear the default command line
+        ""
+        "${pkgs.systemd}/lib/systemd/systemd-journal-gatewayd ${cliArgs}"
+    ];
+
+    systemd.sockets.systemd-journal-gatewayd = {
+      wantedBy = [ "sockets.target" ];
+      listenStreams = [
+        # Clear the default port
+        ""
+        (toString cfg.port)
+      ];
+    };
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/journald-remote.nix b/nixos/modules/system/boot/systemd/journald-remote.nix
new file mode 100644
index 0000000000000..57a0a133e1c6d
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/journald-remote.nix
@@ -0,0 +1,163 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.journald.remote;
+  format = pkgs.formats.systemd;
+
+  cliArgs = lib.cli.toGNUCommandLineShell { } {
+    inherit (cfg) output;
+    # "-3" specifies the file descriptor from the .socket unit.
+    "listen-${cfg.listen}" = "-3";
+  };
+in
+{
+  meta.maintainers = [ lib.maintainers.raitobezarius ];
+  options.services.journald.remote = {
+    enable = lib.mkEnableOption "receiving systemd journals from the network";
+
+    listen = lib.mkOption {
+      default = "https";
+      type = lib.types.enum [ "https" "http" ];
+      description = lib.mdDoc ''
+        Which protocol to listen to.
+      '';
+    };
+
+    output = lib.mkOption {
+      default = "/var/log/journal/remote/";
+      type = lib.types.str;
+      description = lib.mdDoc ''
+        The location of the output journal.
+
+        In case the output file is not specified, journal files will be created
+        underneath the selected directory. Files will be called
+        {file}`remote-hostname.journal`, where the `hostname` part is the
+        escaped hostname of the source endpoint of the connection, or the
+        numerical address if the hostname cannot be determined.
+      '';
+    };
+
+    port = lib.mkOption {
+      default = 19532;
+      type = lib.types.port;
+      description = ''
+        The port to listen to.
+
+        Note that this option is used only if
+        {option}`services.journald.upload.listen` is configured to be either
+        "https" or "http".
+      '';
+    };
+
+    settings = lib.mkOption {
+      default = { };
+
+      description = lib.mdDoc ''
+        Configuration in the journal-remote configuration file. See
+        {manpage}`journal-remote.conf(5)` for available options.
+      '';
+
+      type = lib.types.submodule {
+        freeformType = format.type;
+
+        options.Remote = {
+          Seal = lib.mkOption {
+            default = false;
+            example = true;
+            type = lib.types.bool;
+            description = ''
+              Periodically sign the data in the journal using Forward Secure
+              Sealing.
+            '';
+          };
+
+          SplitMode = lib.mkOption {
+            default = "host";
+            example = "none";
+            type = lib.types.enum [ "host" "none" ];
+            description = lib.mdDoc ''
+              With "host", a separate output file is used, based on the
+              hostname of the other endpoint of a connection. With "none", only
+              one output journal file is used.
+            '';
+          };
+
+          ServerKeyFile = lib.mkOption {
+            default = "/etc/ssl/private/journal-remote.pem";
+            type = lib.types.str;
+            description = lib.mdDoc ''
+              A path to a SSL secret key file in PEM format.
+
+              Note that due to security reasons, `systemd-journal-remote` will
+              refuse files from the world-readable `/nix/store`. This file
+              should be readable by the "" user.
+
+              This option can be used with `listen = "https"`. If the path
+              refers to an `AF_UNIX` stream socket in the file system a
+              connection is made to it and the key read from it.
+            '';
+          };
+
+          ServerCertificateFile = lib.mkOption {
+            default = "/etc/ssl/certs/journal-remote.pem";
+            type = lib.types.str;
+            description = lib.mdDoc ''
+              A path to a SSL certificate file in PEM format.
+
+              This option can be used with `listen = "https"`. If the path
+              refers to an `AF_UNIX` stream socket in the file system a
+              connection is made to it and the certificate read from it.
+            '';
+          };
+
+          TrustedCertificateFile = lib.mkOption {
+            default = "/etc/ssl/ca/trusted.pem";
+            type = lib.types.str;
+            description = lib.mdDoc ''
+              A path to a SSL CA certificate file in PEM format, or `all`.
+
+              If `all` is set, then client certificate checking will be
+              disabled.
+
+              This option can be used with `listen = "https"`. If the path
+              refers to an `AF_UNIX` stream socket in the file system a
+              connection is made to it and the certificate read from it.
+            '';
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.additionalUpstreamSystemUnits = [
+      "systemd-journal-remote.service"
+      "systemd-journal-remote.socket"
+    ];
+
+    systemd.services.systemd-journal-remote.serviceConfig.ExecStart = [
+      # Clear the default command line
+      ""
+      "${pkgs.systemd}/lib/systemd/systemd-journal-remote ${cliArgs}"
+    ];
+
+    systemd.sockets.systemd-journal-remote = {
+      wantedBy = [ "sockets.target" ];
+      listenStreams = [
+        # Clear the default port
+        ""
+        (toString cfg.port)
+      ];
+    };
+
+    # User and group used by systemd-journal-remote.service
+    users.groups.systemd-journal-remote = { };
+    users.users.systemd-journal-remote = {
+      isSystemUser = true;
+      group = "systemd-journal-remote";
+    };
+
+    environment.etc."systemd/journal-remote.conf".source =
+      format.generate "journal-remote.conf" cfg.settings;
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/journald-upload.nix b/nixos/modules/system/boot/systemd/journald-upload.nix
new file mode 100644
index 0000000000000..6421e5fa486f9
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/journald-upload.nix
@@ -0,0 +1,111 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.journald.upload;
+  format = pkgs.formats.systemd;
+in
+{
+  meta.maintainers = [ lib.maintainers.raitobezarius ];
+  options.services.journald.upload = {
+    enable = lib.mkEnableOption "uploading the systemd journal to a remote server";
+
+    settings = lib.mkOption {
+      default = { };
+
+      description = lib.mdDoc ''
+        Configuration for journal-upload. See {manpage}`journal-upload.conf(5)`
+        for available options.
+      '';
+
+      type = lib.types.submodule {
+        freeformType = format.type;
+
+        options.Upload = {
+          URL = lib.mkOption {
+            type = lib.types.str;
+            example = "https://192.168.1.1";
+            description = ''
+              The URL to upload the journal entries to.
+
+              See the description of `--url=` option in
+              {manpage}`systemd-journal-upload(8)` for the description of
+              possible values.
+            '';
+          };
+
+          ServerKeyFile = lib.mkOption {
+            type = with lib.types; nullOr str;
+            example = lib.literalExpression "./server-key.pem";
+            # Since systemd-journal-upload uses a DynamicUser, permissions must
+            # be done using groups
+            description = ''
+              SSL key in PEM format.
+
+              In contrary to what the name suggests, this option configures the
+              client private key sent to the remote journal server.
+
+              This key should not be world-readable, and must be readably by
+              the `systemd-journal` group.
+            '';
+            default = null;
+          };
+
+          ServerCertificateFile = lib.mkOption {
+            type = with lib.types; nullOr str;
+            example = lib.literalExpression "./server-ca.pem";
+            description = ''
+              SSL CA certificate in PEM format.
+
+              In contrary to what the name suggests, this option configures the
+              client certificate sent to the remote journal server.
+            '';
+            default = null;
+          };
+
+          TrustedCertificateFile = lib.mkOption {
+            type = with lib.types; nullOr str;
+            example = lib.literalExpression "./ca";
+            description = ''
+              SSL CA certificate.
+
+              This certificate will be used to check the remote journal HTTPS
+              server certificate.
+            '';
+            default = null;
+          };
+
+          NetworkTimeoutSec = lib.mkOption {
+            type = with lib.types; nullOr str;
+            example = "1s";
+            description = ''
+              When network connectivity to the server is lost, this option
+              configures the time to wait for the connectivity to get restored.
+
+              If the server is not reachable over the network for the
+              configured time, `systemd-journal-upload` exits. Takes a value in
+              seconds (or in other time units if suffixed with "ms", "min",
+              "h", etc). For details, see {manpage}`systemd.time(5)`.
+            '';
+            default = null;
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.additionalUpstreamSystemUnits = [ "systemd-journal-upload.service" ];
+
+    systemd.services."systemd-journal-upload" = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Restart = "always";
+        # To prevent flooding the server in case the server is struggling
+        RestartSec = "3sec";
+      };
+    };
+
+    environment.etc."systemd/journal-upload.conf".source =
+      format.generate "journal-upload.conf" cfg.settings;
+  };
+}
diff --git a/nixos/modules/system/boot/systemd/journald.nix b/nixos/modules/system/boot/systemd/journald.nix
index 773163bbcb811..9a8e7d5926036 100644
--- a/nixos/modules/system/boot/systemd/journald.nix
+++ b/nixos/modules/system/boot/systemd/journald.nix
@@ -5,6 +5,10 @@ with lib;
 let
   cfg = config.services.journald;
 in {
+  imports = [
+    (mkRenamedOptionModule [ "services" "journald" "enableHttpGateway" ] [ "services" "journald" "gateway" "enable" ])
+  ];
+
   options = {
     services.journald.console = mkOption {
       default = "";
@@ -28,6 +32,15 @@ in {
       '';
     };
 
+    services.journald.storage = mkOption {
+      default = "persistent";
+      type = types.enum [ "persistent" "volatile" "auto" "none" ];
+      description = mdDoc ''
+        Controls where to store journal data. See
+        {manpage}`journald.conf(5)` for further information.
+      '';
+    };
+
     services.journald.rateLimitBurst = mkOption {
       default = 10000;
       type = types.int;
@@ -62,14 +75,6 @@ in {
       '';
     };
 
-    services.journald.enableHttpGateway = mkOption {
-      default = false;
-      type = types.bool;
-      description = lib.mdDoc ''
-        Whether to enable the HTTP gateway to the journal.
-      '';
-    };
-
     services.journald.forwardToSyslog = mkOption {
       default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
       defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
@@ -92,15 +97,12 @@ in {
       ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [
       "systemd-journald-dev-log.socket"
       "syslog.socket"
-      ] ++ optionals cfg.enableHttpGateway [
-      "systemd-journal-gatewayd.socket"
-      "systemd-journal-gatewayd.service"
       ];
 
     environment.etc = {
       "systemd/journald.conf".text = ''
         [Journal]
-        Storage=persistent
+        Storage=${cfg.storage}
         RateLimitInterval=${cfg.rateLimitInterval}
         RateLimitBurst=${toString cfg.rateLimitBurst}
         ${optionalString (cfg.console != "") ''
@@ -115,12 +117,6 @@ in {
     };
 
     users.groups.systemd-journal.gid = config.ids.gids.systemd-journal;
-    users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
-    users.users.systemd-journal-gateway.group = "systemd-journal-gateway";
-    users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
-
-    systemd.sockets.systemd-journal-gatewayd.wantedBy =
-      optional cfg.enableHttpGateway "sockets.target";
 
     systemd.services.systemd-journal-flush.restartIfChanged = false;
     systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
diff --git a/nixos/modules/system/boot/systemd/oomd.nix b/nixos/modules/system/boot/systemd/oomd.nix
index fad755e278c77..000b18c01609a 100644
--- a/nixos/modules/system/boot/systemd/oomd.nix
+++ b/nixos/modules/system/boot/systemd/oomd.nix
@@ -3,14 +3,18 @@
   cfg = config.systemd.oomd;
 
 in {
+  imports = [
+    (lib.mkRenamedOptionModule [ "systemd" "oomd" "enableUserServices" ] [ "systemd" "oomd" "enableUserSlices" ])
+  ];
+
   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
+    # https://src.fedoraproject.org/rpms/systemd/tree/806c95e1c70af18f81d499b24cd7acfa4c36ffd6
     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`)");
+    enableUserSlices = lib.mkEnableOption (lib.mdDoc "oomd on all user slices (`user@.slice`) and all user owned slices");
 
     extraConfig = lib.mkOption {
       type = with lib.types; attrsOf (oneOf [ str int bool ]);
@@ -44,14 +48,24 @@ in {
     users.groups.systemd-oom = { };
 
     systemd.slices."-".sliceConfig = lib.mkIf cfg.enableRootSlice {
-      ManagedOOMSwap = "kill";
+      ManagedOOMMemoryPressure = "kill";
+      ManagedOOMMemoryPressureLimit = "80%";
     };
     systemd.slices."system".sliceConfig = lib.mkIf cfg.enableSystemSlice {
-      ManagedOOMSwap = "kill";
+      ManagedOOMMemoryPressure = "kill";
+      ManagedOOMMemoryPressureLimit = "80%";
     };
-    systemd.services."user@".serviceConfig = lib.mkIf cfg.enableUserServices {
+    systemd.slices."user-".sliceConfig = lib.mkIf cfg.enableUserSlices {
       ManagedOOMMemoryPressure = "kill";
-      ManagedOOMMemoryPressureLimit = "50%";
+      ManagedOOMMemoryPressureLimit = "80%";
+    };
+    systemd.user.units."slice" = lib.mkIf cfg.enableUserSlices {
+      text = ''
+        [Slice]
+        ManagedOOMMemoryPressure=kill
+        ManagedOOMMemoryPressureLimit=80%
+      '';
+      overrideStrategy = "asDropin";
     };
   };
 }
diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix
index 2431c68ea17b8..3be744acd0b3b 100644
--- a/nixos/modules/system/boot/systemd/repart.nix
+++ b/nixos/modules/system/boot/systemd/repart.nix
@@ -74,6 +74,18 @@ in
   };
 
   config = lib.mkIf (cfg.enable || initrdCfg.enable) {
+    assertions = [
+      {
+        assertion = initrdCfg.enable -> config.boot.initrd.systemd.enable;
+        message = ''
+          'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled.
+        '';
+      }
+    ];
+
+    # systemd-repart uses loopback devices for partition creation
+    boot.initrd.availableKernelModules = lib.optional initrdCfg.enable "loop";
+
     boot.initrd.systemd = lib.mkIf initrdCfg.enable {
       additionalUpstreamUnits = [
         "systemd-repart.service"
diff --git a/nixos/modules/system/boot/systemd/sysupdate.nix b/nixos/modules/system/boot/systemd/sysupdate.nix
index b1914a9c4e767..cab35ddf270cb 100644
--- a/nixos/modules/system/boot/systemd/sysupdate.nix
+++ b/nixos/modules/system/boot/systemd/sysupdate.nix
@@ -71,7 +71,7 @@ in
       type = with lib.types; attrsOf format.type;
       default = { };
       example = {
-        "10-uki.conf" = {
+        "10-uki" = {
           Transfer = {
             ProtectVersion = "%A";
           };
diff --git a/nixos/modules/system/boot/systemd/sysusers.nix b/nixos/modules/system/boot/systemd/sysusers.nix
new file mode 100644
index 0000000000000..c619c2d91eb09
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/sysusers.nix
@@ -0,0 +1,169 @@
+{ config, lib, pkgs, utils, ... }:
+
+let
+
+  cfg = config.systemd.sysusers;
+  userCfg = config.users;
+
+  sysusersConfig = pkgs.writeTextDir "00-nixos.conf" ''
+    # Type Name ID GECOS Home directory Shell
+
+    # Users
+    ${lib.concatLines (lib.mapAttrsToList
+      (username: opts:
+        let
+          uid = if opts.uid == null then "-" else toString opts.uid;
+        in
+          ''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}''
+      )
+      userCfg.users)
+    }
+
+    # Groups
+    ${lib.concatLines (lib.mapAttrsToList
+      (groupname: opts: ''g ${groupname} ${if opts.gid == null then "-" else toString opts.gid}'') userCfg.groups)
+    }
+
+    # Group membership
+    ${lib.concatStrings (lib.mapAttrsToList
+      (groupname: opts: (lib.concatMapStrings (username: "m ${username} ${groupname}\n")) opts.members ) userCfg.groups)
+    }
+  '';
+
+  staticSysusersCredentials = pkgs.runCommand "static-sysusers-credentials" { } ''
+    mkdir $out; cd $out
+    ${lib.concatLines (
+      (lib.mapAttrsToList
+        (username: opts: "echo -n '${opts.initialHashedPassword}' > 'passwd.hashed-password.${username}'")
+        (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
+        ++
+      (lib.mapAttrsToList
+        (username: opts: "echo -n '${opts.initialPassword}' > 'passwd.plaintext-password.${username}'")
+        (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
+        ++
+      (lib.mapAttrsToList
+        (username: opts: "cat '${opts.hashedPasswordFile}' > 'passwd.hashed-password.${username}'")
+        (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users))
+      )
+    }
+  '';
+
+  staticSysusers = pkgs.runCommand "static-sysusers"
+    {
+      nativeBuildInputs = [ pkgs.systemd ];
+    } ''
+    mkdir $out
+    export CREDENTIALS_DIRECTORY=${staticSysusersCredentials}
+    systemd-sysusers --root $out ${sysusersConfig}/00-nixos.conf
+  '';
+
+in
+
+{
+
+  options = {
+
+    # This module doesn't set it's own user options but reuses the ones from
+    # users-groups.nix
+
+    systemd.sysusers = {
+      enable = lib.mkEnableOption (lib.mdDoc "systemd-sysusers") // {
+        description = lib.mdDoc ''
+          If enabled, users are created with systemd-sysusers instead of with
+          the custom `update-users-groups.pl` script.
+
+          Note: This is experimental.
+        '';
+      };
+    };
+
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = config.system.activationScripts.users == "";
+        message = "system.activationScripts.users has to be empty to use systemd-sysusers";
+      }
+      {
+        assertion = config.users.mutableUsers -> config.system.etc.overlay.enable;
+        message = "config.users.mutableUsers requires config.system.etc.overlay.enable.";
+      }
+    ];
+
+    systemd = lib.mkMerge [
+      ({
+
+        # Create home directories, do not create /var/empty even if that's a user's
+        # home.
+        tmpfiles.settings.home-directories = lib.mapAttrs'
+          (username: opts: lib.nameValuePair opts.home {
+            d = {
+              mode = opts.homeMode;
+              user = username;
+              group = opts.group;
+            };
+          })
+          (lib.filterAttrs (_username: opts: opts.home != "/var/empty") userCfg.users);
+      })
+
+      (lib.mkIf config.users.mutableUsers {
+        additionalUpstreamSystemUnits = [
+          "systemd-sysusers.service"
+        ];
+
+        services.systemd-sysusers = {
+          # Enable switch-to-configuration to restart the service.
+          unitConfig.ConditionNeedsUpdate = [ "" ];
+          requiredBy = [ "sysinit-reactivation.target" ];
+          before = [ "sysinit-reactivation.target" ];
+          restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ];
+
+          serviceConfig = {
+            LoadCredential = lib.mapAttrsToList
+              (username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}")
+              (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users);
+            SetCredential = (lib.mapAttrsToList
+              (username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}")
+              (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
+            ++
+            (lib.mapAttrsToList
+              (username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}")
+              (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
+            ;
+          };
+        };
+      })
+    ];
+
+    environment.etc = lib.mkMerge [
+      (lib.mkIf (!userCfg.mutableUsers) {
+        "passwd" = {
+          source = "${staticSysusers}/etc/passwd";
+          mode = "0644";
+        };
+        "group" = {
+          source = "${staticSysusers}/etc/group";
+          mode = "0644";
+        };
+        "shadow" = {
+          source = "${staticSysusers}/etc/shadow";
+          mode = "0000";
+        };
+        "gshadow" = {
+          source = "${staticSysusers}/etc/gshadow";
+          mode = "0000";
+        };
+      })
+
+      (lib.mkIf userCfg.mutableUsers {
+        "sysusers.d".source = sysusersConfig;
+      })
+    ];
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+}
diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix
index 32b9b275d3587..dae23eddd1e2b 100644
--- a/nixos/modules/system/boot/systemd/tmpfiles.nix
+++ b/nixos/modules/system/boot/systemd/tmpfiles.nix
@@ -20,6 +20,102 @@ in
       '';
     };
 
+    systemd.tmpfiles.settings = mkOption {
+      description = lib.mdDoc ''
+        Declare systemd-tmpfiles rules to create, delete, and clean up volatile
+        and temporary files and directories.
+
+        Even though the service is called `*tmp*files` you can also create
+        persistent files.
+      '';
+      example = {
+        "10-mypackage" = {
+          "/var/lib/my-service/statefolder".d = {
+            mode = "0755";
+            user = "root";
+            group = "root";
+          };
+        };
+      };
+      default = {};
+      type = types.attrsOf (types.attrsOf (types.attrsOf (types.submodule ({ name, config, ... }: {
+        options.type = mkOption {
+          type = types.str;
+          default = name;
+          example = "d";
+          description = lib.mdDoc ''
+            The type of operation to perform on the file.
+
+            The type consists of a single letter and optionally one or more
+            modifier characters.
+
+            Please see the upstream documentation for the available types and
+            more details:
+            <https://www.freedesktop.org/software/systemd/man/tmpfiles.d>
+          '';
+        };
+        options.mode = mkOption {
+          type = types.str;
+          default = "-";
+          example = "0755";
+          description = lib.mdDoc ''
+            The file access mode to use when creating this file or directory.
+          '';
+        };
+        options.user = mkOption {
+          type = types.str;
+          default = "-";
+          example = "root";
+          description = lib.mdDoc ''
+            The user of the file.
+
+            This may either be a numeric ID or a user/group name.
+
+            If omitted or when set to `"-"`, the user and group of the user who
+            invokes systemd-tmpfiles is used.
+          '';
+        };
+        options.group = mkOption {
+          type = types.str;
+          default = "-";
+          example = "root";
+          description = lib.mdDoc ''
+            The group of the file.
+
+            This may either be a numeric ID or a user/group name.
+
+            If omitted or when set to `"-"`, the user and group of the user who
+            invokes systemd-tmpfiles is used.
+          '';
+        };
+        options.age = mkOption {
+          type = types.str;
+          default = "-";
+          example = "10d";
+          description = lib.mdDoc ''
+            Delete a file when it reaches a certain age.
+
+            If a file or directory is older than the current time minus the age
+            field, it is deleted.
+
+            If set to `"-"` no automatic clean-up is done.
+          '';
+        };
+        options.argument = mkOption {
+          type = types.str;
+          default = "";
+          example = "";
+          description = lib.mdDoc ''
+            An argument whose meaning depends on the type of operation.
+
+            Please see the upstream documentation for the meaning of this
+            parameter in different situations:
+            <https://www.freedesktop.org/software/systemd/man/tmpfiles.d>
+          '';
+        };
+      }))));
+    };
+
     systemd.tmpfiles.packages = mkOption {
       type = types.listOf types.package;
       default = [];
@@ -54,6 +150,41 @@ in
       "systemd-tmpfiles-setup.service"
     ];
 
+    # Allow systemd-tmpfiles to be restarted by switch-to-configuration. This
+    # service is not pulled into the normal boot process. It only exists for
+    # switch-to-configuration.
+    #
+    # This needs to be a separate unit because it does not execute
+    # systemd-tmpfiles with `--boot` as that is supposed to only be executed
+    # once at boot time.
+    #
+    # Keep this aligned with the upstream `systemd-tmpfiles-setup.service` unit.
+    systemd.services."systemd-tmpfiles-resetup" = {
+      description = "Re-setup tmpfiles on a system that is already running.";
+
+      requiredBy = [ "sysinit-reactivation.target" ];
+      after = [ "local-fs.target" "systemd-sysusers.service" "systemd-journald.service" ];
+      before = [ "sysinit-reactivation.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      restartTriggers = [ config.environment.etc."tmpfiles.d".source ];
+
+      unitConfig.DefaultDependencies = false;
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "systemd-tmpfiles --create --remove --exclude-prefix=/dev";
+        SuccessExitStatus = "DATAERR CANTCREAT";
+        ImportCredential = [
+          "tmpfiles.*"
+          "loging.motd"
+          "login.issue"
+          "network.hosts"
+          "ssh.authorized_keys.root"
+        ];
+      };
+    };
+
     environment.etc = {
       "tmpfiles.d".source = (pkgs.symlinkJoin {
         name = "tmpfiles.d";
@@ -100,7 +231,13 @@ in
           ${concatStringsSep "\n" cfg.rules}
         '';
       })
-    ];
+    ] ++ (mapAttrsToList (name: paths:
+      pkgs.writeTextDir "lib/tmpfiles.d/${name}.conf" (concatStrings (mapAttrsToList (path: types:
+        concatStrings (mapAttrsToList (_type: entry: ''
+          '${entry.type}' '${path}' '${entry.mode}' '${entry.user}' '${entry.group}' '${entry.age}' ${entry.argument}
+        '') types)
+      ) paths ))
+    ) cfg.settings);
 
     systemd.tmpfiles.rules = [
       "d  /nix/var                           0755 root root - -"
diff --git a/nixos/modules/system/boot/systemd/userdbd.nix b/nixos/modules/system/boot/systemd/userdbd.nix
index 994aa3ca3b8c1..e7f6d42341c4e 100644
--- a/nixos/modules/system/boot/systemd/userdbd.nix
+++ b/nixos/modules/system/boot/systemd/userdbd.nix
@@ -5,7 +5,7 @@ let
 in
 {
   options.services.userdbd.enable = lib.mkEnableOption (lib.mdDoc ''
-    Enables the systemd JSON user/group record lookup service
+    the systemd JSON user/group record lookup service
   '');
   config = lib.mkIf cfg.enable {
     systemd.additionalUpstreamSystemUnits = [
diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix
index a6604802c38ca..2666e4cd6b284 100644
--- a/nixos/modules/system/boot/timesyncd.nix
+++ b/nixos/modules/system/boot/timesyncd.nix
@@ -46,6 +46,35 @@ with lib;
       wantedBy = [ "sysinit.target" ];
       aliases = [ "dbus-org.freedesktop.timesync1.service" ];
       restartTriggers = [ config.environment.etc."systemd/timesyncd.conf".source ];
+      # systemd-timesyncd disables DNSSEC validation in the nss-resolve module by setting SYSTEMD_NSS_RESOLVE_VALIDATE to 0 in the unit file.
+      # This is required in order to solve the chicken-and-egg problem when DNSSEC validation needs the correct time to work, but to set the
+      # correct time, we need to connect to an NTP server, which usually requires resolving its hostname.
+      # In order for nss-resolve to be able to read this environment variable we patch systemd-timesyncd to disable NSCD and use NSS modules directly.
+      # This means that systemd-timesyncd needs to have NSS modules path in LD_LIBRARY_PATH. When systemd-resolved is disabled we still need to set
+      # NSS module path so that systemd-timesyncd keeps using other NSS modules that are configured in the system.
+      environment.LD_LIBRARY_PATH = config.system.nssModules.path;
+
+      preStart = (
+        # Ensure that we have some stored time to prevent
+        # systemd-timesyncd to resort back to the fallback time.  If
+        # the file doesn't exist we assume that our current system
+        # clock is good enough to provide an initial value.
+        ''
+          if ! [ -f /var/lib/systemd/timesync/clock ]; then
+            test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync
+            touch /var/lib/systemd/timesync/clock
+          fi
+        '' +
+        # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
+        #  - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
+        #  - https://github.com/systemd/systemd/issues/12131
+        (lib.optionalString (versionOlder config.system.stateVersion "19.09") ''
+          if [ -L /var/lib/systemd/timesync ]; then
+            rm /var/lib/systemd/timesync
+            mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
+          fi
+        '')
+      );
     };
 
     environment.etc."systemd/timesyncd.conf".text = ''
@@ -59,28 +88,5 @@ with lib;
       group = "systemd-timesync";
     };
     users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
-
-    system.activationScripts.systemd-timesyncd-migration =
-      # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
-      #  - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
-      #  - https://github.com/systemd/systemd/issues/12131
-      mkIf (versionOlder config.system.stateVersion "19.09") ''
-        if [ -L /var/lib/systemd/timesync ]; then
-          rm /var/lib/systemd/timesync
-          mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
-        fi
-      '';
-    system.activationScripts.systemd-timesyncd-init-clock =
-      # Ensure that we have some stored time to prevent systemd-timesyncd to
-      # resort back to the fallback time.
-      # If the file doesn't exist we assume that our current system clock is
-      # good enough to provide an initial value.
-      ''
-      if ! [ -f /var/lib/systemd/timesync/clock ]; then
-        test -d /var/lib/systemd/timesync || mkdir -p /var/lib/systemd/timesync
-        touch /var/lib/systemd/timesync/clock
-      fi
-      '';
   };
-
 }
diff --git a/nixos/modules/system/boot/uki.nix b/nixos/modules/system/boot/uki.nix
new file mode 100644
index 0000000000000..63c4e0c0e3913
--- /dev/null
+++ b/nixos/modules/system/boot/uki.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  cfg = config.boot.uki;
+
+  inherit (pkgs.stdenv.hostPlatform) efiArch;
+
+  format = pkgs.formats.ini { };
+  ukifyConfig = format.generate "ukify.conf" cfg.settings;
+
+in
+
+{
+  options = {
+
+    boot.uki = {
+      name = lib.mkOption {
+        type = lib.types.str;
+        description = lib.mdDoc "Name of the UKI";
+      };
+
+      version = lib.mkOption {
+        type = lib.types.nullOr lib.types.str;
+        default = config.system.image.version;
+        defaultText = lib.literalExpression "config.system.image.version";
+        description = lib.mdDoc "Version of the image or generation the UKI belongs to";
+      };
+
+      settings = lib.mkOption {
+        type = format.type;
+        description = lib.mdDoc ''
+          The configuration settings for ukify. These control what the UKI
+          contains and how it is built.
+        '';
+      };
+    };
+
+    system.boot.loader.ukiFile = lib.mkOption {
+      type = lib.types.str;
+      internal = true;
+      description = lib.mdDoc "Name of the UKI file";
+    };
+
+  };
+
+  config = {
+
+    boot.uki.name = lib.mkOptionDefault (if config.system.image.id != null then
+      config.system.image.id
+    else
+      "nixos");
+
+    boot.uki.settings = lib.mkOptionDefault {
+      UKI = {
+        Linux = "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
+        Initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
+        Cmdline = "init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}";
+        Stub = "${pkgs.systemd}/lib/systemd/boot/efi/linux${efiArch}.efi.stub";
+        Uname = "${config.boot.kernelPackages.kernel.modDirVersion}";
+        OSRelease = "@${config.system.build.etc}/etc/os-release";
+        # This is needed for cross compiling.
+        EFIArch = efiArch;
+      };
+    };
+
+    system.boot.loader.ukiFile =
+      let
+        name = config.boot.uki.name;
+        version = config.boot.uki.version;
+        versionInfix = if version != null then "_${version}" else "";
+      in
+      name + versionInfix + ".efi";
+
+    system.build.uki = pkgs.runCommand config.system.boot.loader.ukiFile { } ''
+      mkdir -p $out
+      ${pkgs.buildPackages.systemdUkify}/lib/systemd/ukify build \
+        --config=${ukifyConfig} \
+        --output="$out/${config.system.boot.loader.ukiFile}"
+    '';
+
+    meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  };
+}
diff --git a/nixos/modules/system/boot/unl0kr.nix b/nixos/modules/system/boot/unl0kr.nix
new file mode 100644
index 0000000000000..8d9af37382e01
--- /dev/null
+++ b/nixos/modules/system/boot/unl0kr.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.boot.initrd.unl0kr;
+in
+{
+  options.boot.initrd.unl0kr = {
+    enable = lib.mkEnableOption (lib.mdDoc "unl0kr in initrd") // {
+      description = lib.mdDoc ''
+        Whether to enable the unl0kr on-screen keyboard in initrd to unlock LUKS.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    meta.maintainers = with lib.maintainers; [ tomfitzhenry ];
+    assertions = [
+      {
+        assertion = cfg.enable -> config.boot.initrd.systemd.enable;
+        message = "boot.initrd.unl0kr is only supported with boot.initrd.systemd.";
+      }
+    ];
+
+    boot.initrd.systemd = {
+      storePaths = with pkgs; [
+        "${pkgs.gnugrep}/bin/grep"
+        libinput
+        xkeyboard_config
+        "${config.boot.initrd.systemd.package}/lib/systemd/systemd-reply-password"
+        "${pkgs.unl0kr}/bin/unl0kr"
+      ];
+      services = {
+        unl0kr-ask-password = {
+          description = "Forward Password Requests to unl0kr";
+          conflicts = [
+            "emergency.service"
+            "initrd-switch-root.target"
+            "shutdown.target"
+          ];
+          unitConfig.DefaultDependencies = false;
+          after = [
+            "systemd-vconsole-setup.service"
+            "udev.service"
+          ];
+          before = [
+            "shutdown.target"
+          ];
+          script = ''
+            # This script acts as a Password Agent: https://systemd.io/PASSWORD_AGENTS/
+
+            DIR=/run/systemd/ask-password/
+            # If a user has multiple encrypted disks, the requests might come in different times,
+            # so make sure to answer as many requests as we can. Once boot succeeds, other
+            # password agents will be responsible for watching for requests.
+            while [ -d $DIR ] && [ "$(ls -A $DIR/ask.*)" ];
+            do
+              for file in `ls $DIR/ask.*`; do
+                socket="$(cat "$file" | ${pkgs.gnugrep}/bin/grep "Socket=" | cut -d= -f2)"
+                ${pkgs.unl0kr}/bin/unl0kr | ${config.boot.initrd.systemd.package}/lib/systemd/systemd-reply-password 1 "$socket"
+              done
+            done
+          '';
+        };
+      };
+
+      paths = {
+        unl0kr-ask-password = {
+          description = "Forward Password Requests to unl0kr";
+          conflicts = [
+            "emergency.service"
+            "initrd-switch-root.target"
+            "shutdown.target"
+          ];
+          unitConfig.DefaultDependencies = false;
+          before = [
+            "shutdown.target"
+            "paths.target"
+            "cryptsetup.target"
+          ];
+          wantedBy = [ "sysinit.target" ];
+          pathConfig = {
+            DirectoryNotEmpty = "/run/systemd/ask-password";
+            MakeDirectory = true;
+          };
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/system/etc/build-composefs-dump.py b/nixos/modules/system/etc/build-composefs-dump.py
new file mode 100644
index 0000000000000..923d40008b63f
--- /dev/null
+++ b/nixos/modules/system/etc/build-composefs-dump.py
@@ -0,0 +1,209 @@
+#!/usr/bin/env python3
+
+"""Build a composefs dump from a Json config
+
+See the man page of composefs-dump for details about the format:
+https://github.com/containers/composefs/blob/main/man/composefs-dump.md
+
+Ensure to check the file with the check script when you make changes to it:
+
+./check-build-composefs-dump.sh ./build-composefs_dump.py
+"""
+
+import glob
+import json
+import os
+import sys
+from enum import Enum
+from pathlib import Path
+from typing import Any
+
+Attrs = dict[str, Any]
+
+
+class FileType(Enum):
+    """The filetype as defined by the `st_mode` stat field in octal
+
+    You can check the st_mode stat field of a path in Python with
+    `oct(os.stat("/path/").st_mode)`
+    """
+
+    directory = "4"
+    file = "10"
+    symlink = "12"
+
+
+class ComposefsPath:
+    path: str
+    size: int
+    filetype: FileType
+    mode: str
+    uid: str
+    gid: str
+    payload: str
+    rdev: str = "0"
+    nlink: int = 1
+    mtime: str = "1.0"
+    content: str = "-"
+    digest: str = "-"
+
+    def __init__(
+        self,
+        attrs: Attrs,
+        size: int,
+        filetype: FileType,
+        mode: str,
+        payload: str,
+        path: str | None = None,
+    ):
+        if path is None:
+            path = attrs["target"]
+        self.path = "/" + path
+        self.size = size
+        self.filetype = filetype
+        self.mode = mode
+        self.uid = attrs["uid"]
+        self.gid = attrs["gid"]
+        self.payload = payload
+
+    def write_line(self) -> str:
+        line_list = [
+            str(self.path),
+            str(self.size),
+            f"{self.filetype.value}{self.mode}",
+            str(self.nlink),
+            str(self.uid),
+            str(self.gid),
+            str(self.rdev),
+            str(self.mtime),
+            str(self.payload),
+            str(self.content),
+            str(self.digest),
+        ]
+        return " ".join(line_list)
+
+
+def eprint(*args, **kwargs) -> None:
+    print(args, **kwargs, file=sys.stderr)
+
+
+def leading_directories(path: str) -> list[str]:
+    """Return the leading directories of path
+
+    Given the path "alsa/conf.d/50-pipewire.conf", for example, this function
+    returns `[ "alsa", "alsa/conf.d" ]`.
+    """
+    parents = list(Path(path).parents)
+    parents.reverse()
+    # remove the implicit `.` from the start of a relative path or `/` from an
+    # absolute path
+    del parents[0]
+    return [str(i) for i in parents]
+
+
+def add_leading_directories(
+    target: str, attrs: Attrs, paths: dict[str, ComposefsPath]
+) -> None:
+    """Add the leading directories of a target path to the composefs paths
+
+    mkcomposefs expects that all leading directories are explicitly listed in
+    the dump file. Given the path "alsa/conf.d/50-pipewire.conf", for example,
+    this function adds "alsa" and "alsa/conf.d" to the composefs paths.
+    """
+    path_components = leading_directories(target)
+    for component in path_components:
+        composefs_path = ComposefsPath(
+            attrs,
+            path=component,
+            size=4096,
+            filetype=FileType.directory,
+            mode="0755",
+            payload="-",
+        )
+        paths[component] = composefs_path
+
+
+def main() -> None:
+    """Build a composefs dump from a Json config
+
+    This config describes the files that the final composefs image is supposed
+    to contain.
+    """
+    config_file = sys.argv[1]
+    if not config_file:
+        eprint("No config file was supplied.")
+        sys.exit(1)
+
+    with open(config_file, "rb") as f:
+        config = json.load(f)
+
+    if not config:
+        eprint("Config is empty.")
+        sys.exit(1)
+
+    eprint("Building composefs dump...")
+
+    paths: dict[str, ComposefsPath] = {}
+    for attrs in config:
+        target = attrs["target"]
+        source = attrs["source"]
+        mode = attrs["mode"]
+
+        if "*" in source:  # Path with globbing
+            glob_sources = glob.glob(source)
+            for glob_source in glob_sources:
+                basename = os.path.basename(glob_source)
+                glob_target = f"{target}/{basename}"
+
+                composefs_path = ComposefsPath(
+                    attrs,
+                    path=glob_target,
+                    size=100,
+                    filetype=FileType.symlink,
+                    mode="0777",
+                    payload=glob_source,
+                )
+
+                paths[glob_target] = composefs_path
+                add_leading_directories(glob_target, attrs, paths)
+        else:  # Without globbing
+            if mode == "symlink":
+                composefs_path = ComposefsPath(
+                    attrs,
+                    # A high approximation of the size of a symlink
+                    size=100,
+                    filetype=FileType.symlink,
+                    mode="0777",
+                    payload=source,
+                )
+            else:
+                if os.path.isdir(source):
+                    composefs_path = ComposefsPath(
+                        attrs,
+                        size=4096,
+                        filetype=FileType.directory,
+                        mode=mode,
+                        payload=source,
+                    )
+                else:
+                    composefs_path = ComposefsPath(
+                        attrs,
+                        size=os.stat(source).st_size,
+                        filetype=FileType.file,
+                        mode=mode,
+                        payload=target,
+                    )
+            paths[target] = composefs_path
+            add_leading_directories(target, attrs, paths)
+
+    composefs_dump = ["/ 4096 40755 1 0 0 0 0.0 - - -"]  # Root directory
+    for key in sorted(paths):
+        composefs_path = paths[key]
+        eprint(composefs_path.path)
+        composefs_dump.append(composefs_path.write_line())
+
+    print("\n".join(composefs_dump))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/nixos/modules/system/etc/check-build-composefs-dump.sh b/nixos/modules/system/etc/check-build-composefs-dump.sh
new file mode 100755
index 0000000000000..da61651d1a5d6
--- /dev/null
+++ b/nixos/modules/system/etc/check-build-composefs-dump.sh
@@ -0,0 +1,8 @@
+#! /usr/bin/env nix-shell
+#! nix-shell -i bash -p black ruff mypy
+
+file=$1
+
+black --check --diff $file
+ruff --line-length 88 $file
+mypy --strict $file
diff --git a/nixos/modules/system/etc/etc-activation.nix b/nixos/modules/system/etc/etc-activation.nix
index 7801049501860..f47fd771c6592 100644
--- a/nixos/modules/system/etc/etc-activation.nix
+++ b/nixos/modules/system/etc/etc-activation.nix
@@ -1,12 +1,96 @@
 { config, lib, ... }:
-let
-  inherit (lib) stringAfter;
-in {
+
+{
 
   imports = [ ./etc.nix ];
 
-  config = {
-    system.activationScripts.etc =
-      stringAfter [ "users" "groups" ] config.system.build.etcActivationCommands;
-  };
+  config = lib.mkMerge [
+
+    {
+      system.activationScripts.etc =
+        lib.stringAfter [ "users" "groups" ] config.system.build.etcActivationCommands;
+    }
+
+    (lib.mkIf config.system.etc.overlay.enable {
+
+      assertions = [
+        {
+          assertion = config.boot.initrd.systemd.enable;
+          message = "`system.etc.overlay.enable` requires `boot.initrd.systemd.enable`";
+        }
+        {
+          assertion = (!config.system.etc.overlay.mutable) -> config.systemd.sysusers.enable;
+          message = "`system.etc.overlay.mutable = false` requires `systemd.sysusers.enable`";
+        }
+        {
+          assertion = lib.versionAtLeast config.boot.kernelPackages.kernel.version "6.6";
+          message = "`system.etc.overlay.enable requires a newer kernel, at least version 6.6";
+        }
+        {
+          assertion = config.systemd.sysusers.enable -> (config.users.mutableUsers == config.system.etc.overlay.mutable);
+          message = ''
+            When using systemd-sysusers and mounting `/etc` via an overlay, users
+            can only be mutable when `/etc` is mutable and vice versa.
+          '';
+        }
+      ];
+
+      boot.initrd.availableKernelModules = [ "loop" "erofs" "overlay" ];
+
+      boot.initrd.systemd = {
+        mounts = [
+          {
+            where = "/run/etc-metadata";
+            what = "/sysroot${config.system.build.etcMetadataImage}";
+            type = "erofs";
+            options = "loop";
+            unitConfig.RequiresMountsFor = [
+              "/sysroot/nix/store"
+            ];
+          }
+          {
+            where = "/sysroot/etc";
+            what = "overlay";
+            type = "overlay";
+            options = lib.concatStringsSep "," ([
+              "relatime"
+              "redirect_dir=on"
+              "metacopy=on"
+              "lowerdir=/run/etc-metadata::/sysroot${config.system.build.etcBasedir}"
+            ] ++ lib.optionals config.system.etc.overlay.mutable [
+              "rw"
+              "upperdir=/sysroot/.rw-etc/upper"
+              "workdir=/sysroot/.rw-etc/work"
+            ] ++ lib.optionals (!config.system.etc.overlay.mutable) [
+              "ro"
+            ]);
+            wantedBy = [ "initrd-fs.target" ];
+            before = [ "initrd-fs.target" ];
+            requires = lib.mkIf config.system.etc.overlay.mutable [ "rw-etc.service" ];
+            after = lib.mkIf config.system.etc.overlay.mutable [ "rw-etc.service" ];
+            unitConfig.RequiresMountsFor = [
+              "/sysroot/nix/store"
+              "/run/etc-metadata"
+            ];
+          }
+        ];
+        services = lib.mkIf config.system.etc.overlay.mutable {
+          rw-etc = {
+            unitConfig = {
+              DefaultDependencies = false;
+              RequiresMountsFor = "/sysroot";
+            };
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = ''
+                /bin/mkdir -p -m 0755 /sysroot/.rw-etc/upper /sysroot/.rw-etc/work
+              '';
+            };
+          };
+        };
+      };
+
+    })
+
+  ];
 }
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
index ea61e7384e60c..baf37ba6def34 100644
--- a/nixos/modules/system/etc/etc.nix
+++ b/nixos/modules/system/etc/etc.nix
@@ -62,6 +62,16 @@ let
     ]) etc'}
   '';
 
+  etcHardlinks = filter (f: f.mode != "symlink") etc';
+
+  build-composefs-dump = pkgs.runCommand "build-composefs-dump.py"
+    {
+      buildInputs = [ pkgs.python3 ];
+    } ''
+    install ${./build-composefs-dump.py} $out
+    patchShebangs --host $out
+  '';
+
 in
 
 {
@@ -72,6 +82,30 @@ in
 
   options = {
 
+    system.etc.overlay = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          Mount `/etc` as an overlayfs instead of generating it via a perl script.
+
+          Note: This is currently experimental. Only enable this option if you're
+          confident that you can recover your system if it breaks.
+        '';
+      };
+
+      mutable = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Whether to mount `/etc` mutably (i.e. read-write) or immutably (i.e. read-only).
+
+          If this is false, only the immutable lowerdir is mounted. If it is
+          true, a writable upperdir is mounted on top.
+        '';
+      };
+    };
+
     environment.etc = mkOption {
       default = {};
       example = literalExpression ''
@@ -190,12 +224,84 @@ in
   config = {
 
     system.build.etc = etc;
-    system.build.etcActivationCommands =
-      ''
-        # Set up the statically computed bits of /etc.
-        echo "setting up /etc..."
-        ${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl ${./setup-etc.pl} ${etc}/etc
+    system.build.etcActivationCommands = let
+      etcOverlayOptions = lib.concatStringsSep "," ([
+        "relatime"
+        "redirect_dir=on"
+        "metacopy=on"
+      ] ++ lib.optionals config.system.etc.overlay.mutable [
+        "upperdir=/.rw-etc/upper"
+        "workdir=/.rw-etc/work"
+      ]);
+    in if config.system.etc.overlay.enable then ''
+      # This script atomically remounts /etc when switching configuration. On a (re-)boot
+      # this should not run because /etc is mounted via a systemd mount unit
+      # instead. To a large extent this mimics what composefs does. Because
+      # it's relatively simple, however, we avoid the composefs dependency.
+      if [[ ! $IN_NIXOS_SYSTEMD_STAGE1 ]]; then
+        echo "remounting /etc..."
+
+        tmpMetadataMount=$(mktemp --directory)
+        mount --type erofs ${config.system.build.etcMetadataImage} $tmpMetadataMount
+
+        # Mount the new /etc overlay to a temporary private mount.
+        # This needs the indirection via a private bind mount because you
+        # cannot move shared mounts.
+        tmpEtcMount=$(mktemp --directory)
+        mount --bind --make-private $tmpEtcMount $tmpEtcMount
+        mount --type overlay overlay \
+          --options lowerdir=$tmpMetadataMount::${config.system.build.etcBasedir},${etcOverlayOptions} \
+          $tmpEtcMount
+
+        # Move the new temporary /etc mount underneath the current /etc mount.
+        #
+        # This should eventually use util-linux to perform this move beneath,
+        # however, this functionality is not yet in util-linux. See this
+        # tracking issue: https://github.com/util-linux/util-linux/issues/2604
+        ${pkgs.move-mount-beneath}/bin/move-mount --move --beneath $tmpEtcMount /etc
+
+        # Unmount the top /etc mount to atomically reveal the new mount.
+        umount /etc
+
+      fi
+    '' else ''
+      # Set up the statically computed bits of /etc.
+      echo "setting up /etc..."
+      ${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl ${./setup-etc.pl} ${etc}/etc
+    '';
+
+    system.build.etcBasedir = pkgs.runCommandLocal "etc-lowerdir" { } ''
+      set -euo pipefail
+
+      makeEtcEntry() {
+        src="$1"
+        target="$2"
+
+        mkdir -p "$out/$(dirname "$target")"
+        cp "$src" "$out/$target"
+      }
+
+      mkdir -p "$out"
+      ${concatMapStringsSep "\n" (etcEntry: escapeShellArgs [
+        "makeEtcEntry"
+        # Force local source paths to be added to the store
+        "${etcEntry.source}"
+        etcEntry.target
+      ]) etcHardlinks}
+    '';
+
+    system.build.etcMetadataImage =
+      let
+        etcJson = pkgs.writeText "etc-json" (builtins.toJSON etc');
+        etcDump = pkgs.runCommand "etc-dump" { } "${build-composefs-dump} ${etcJson} > $out";
+      in
+      pkgs.runCommand "etc-metadata.erofs" {
+        nativeBuildInputs = [ pkgs.composefs pkgs.erofs-utils ];
+      } ''
+        mkcomposefs --from-file ${etcDump} $out
+        fsck.erofs $out
       '';
+
   };
 
 }
diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index 29e3e313336f5..22311871274b9 100644
--- a/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -109,6 +109,17 @@ in {
         '';
       };
 
+      fixedRandomDelay = mkOption {
+        default = false;
+        type = types.bool;
+        example = true;
+        description = lib.mdDoc ''
+          Make the randomized delay consistent between runs.
+          This reduces the jitter between automatic upgrades.
+          See {option}`randomizedDelaySec` for configuring the randomized delay.
+        '';
+      };
+
       rebootWindow = mkOption {
         description = lib.mdDoc ''
           Define a lower and upper time value (in HH:MM format) which
@@ -253,6 +264,7 @@ in {
     systemd.timers.nixos-upgrade = {
       timerConfig = {
         RandomizedDelaySec = cfg.randomizedDelaySec;
+        FixedRandomDelay = cfg.fixedRandomDelay;
         Persistent = cfg.persistent;
       };
     };
diff --git a/nixos/modules/tasks/bcache.nix b/nixos/modules/tasks/bcache.nix
index 35b922dc8a1d3..68531a4d2fed7 100644
--- a/nixos/modules/tasks/bcache.nix
+++ b/nixos/modules/tasks/bcache.nix
@@ -1,6 +1,10 @@
-{ config, lib, pkgs, ... }:
-
-{
+{ config, lib, pkgs, ... }: let
+  cfg = config.boot.bcache;
+in {
+  options.boot.bcache.enable = lib.mkEnableOption (lib.mdDoc "bcache mount support") // {
+    default = true;
+    example = false;
+  };
   options.boot.initrd.services.bcache.enable = lib.mkEnableOption (lib.mdDoc "bcache support in the initrd") // {
     description = lib.mdDoc ''
       *This will only be used when systemd is used in stage 1.*
@@ -9,7 +13,7 @@
     '';
   };
 
-  config = {
+  config = lib.mkIf cfg.enable {
 
     environment.systemPackages = [ pkgs.bcache-tools ];
 
diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix
index 7837a34b49844..da9c83ba339c2 100644
--- a/nixos/modules/tasks/encrypted-devices.nix
+++ b/nixos/modules/tasks/encrypted-devices.nix
@@ -5,8 +5,22 @@ with lib;
 let
   fileSystems = config.system.build.fileSystems ++ config.swapDevices;
   encDevs = filter (dev: dev.encrypted.enable) fileSystems;
-  keyedEncDevs = filter (dev: dev.encrypted.keyFile != null) encDevs;
-  keylessEncDevs = filter (dev: dev.encrypted.keyFile == null) encDevs;
+
+  # With scripted initrd, devices with a keyFile have to be opened
+  # late, after file systems are mounted, because that could be where
+  # the keyFile is located. With systemd initrd, each individual
+  # systemd-cryptsetup@ unit has RequiresMountsFor= to delay until all
+  # the mount units for the key file are done; i.e. no special
+  # treatment is needed.
+  lateEncDevs =
+    if config.boot.initrd.systemd.enable
+    then { }
+    else filter (dev: dev.encrypted.keyFile != null) encDevs;
+  earlyEncDevs =
+    if config.boot.initrd.systemd.enable
+    then encDevs
+    else filter (dev: dev.encrypted.keyFile == null) encDevs;
+
   anyEncrypted =
     foldr (j: v: v || j.encrypted.enable) false encDevs;
 
@@ -39,11 +53,14 @@ let
         type = types.nullOr types.str;
         description = lib.mdDoc ''
           Path to a keyfile used to unlock the backing encrypted
-          device. At the time this keyfile is accessed, the
-          `neededForBoot` filesystems (see
-          `fileSystems.<name?>.neededForBoot`)
-          will have been mounted under `/mnt-root`,
-          so the keyfile path should usually start with "/mnt-root/".
+          device. When systemd stage 1 is not enabled, at the time
+          this keyfile is accessed, the `neededForBoot` filesystems
+          (see `utils.fsNeededForBoot`) will have been mounted under
+          `/mnt-root`, so the keyfile path should usually start with
+          "/mnt-root/". When systemd stage 1 is enabled,
+          `fsNeededForBoot` file systems will be mounted as needed
+          under `/sysroot`, and the keyfile will not be accessed until
+          its requisite mounts are done.
         '';
       };
     };
@@ -62,26 +79,42 @@ in
   };
 
   config = mkIf anyEncrypted {
-    assertions = map (dev: {
-      assertion = dev.encrypted.label != null;
-      message = ''
-        The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set
-      '';
-    }) encDevs;
+    assertions = concatMap (dev: [
+      {
+        assertion = dev.encrypted.label != null;
+        message = ''
+          The filesystem for ${dev.mountPoint} has encrypted.enable set to true, but no encrypted.label set
+        '';
+      }
+      {
+        assertion =
+          config.boot.initrd.systemd.enable -> (
+            dev.encrypted.keyFile == null
+            || !lib.any (x: lib.hasPrefix x dev.encrypted.keyFile) ["/mnt-root" "$targetRoot"]
+          );
+        message = ''
+          Bad use of '/mnt-root' or '$targetRoot` in 'keyFile'.
+
+            When 'boot.initrd.systemd.enable' is enabled, file systems
+            are mounted at '/sysroot' instead of '/mnt-root'.
+        '';
+      }
+    ]) encDevs;
 
     boot.initrd = {
       luks = {
         devices =
           builtins.listToAttrs (map (dev: {
             name = dev.encrypted.label;
-            value = { device = dev.encrypted.blkDev; };
-          }) keylessEncDevs);
+            value = { device = dev.encrypted.blkDev; inherit (dev.encrypted) keyFile; };
+          }) earlyEncDevs);
         forceLuksSupportInInitrd = true;
       };
-      postMountCommands =
-        concatMapStrings (dev:
+      # TODO: systemd stage 1
+      postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
+        (concatMapStrings (dev:
           "cryptsetup luksOpen --key-file ${dev.encrypted.keyFile} ${dev.encrypted.blkDev} ${dev.encrypted.label};\n"
-        ) keyedEncDevs;
+        ) lateEncDevs);
     };
   };
 }
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 7cb2ca23fa417..1378a0090c1df 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -187,9 +187,8 @@ let
       skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck || isBindMount fs;
       # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
       escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
-    in fstabFileSystems: { rootPrefix ? "" }: concatMapStrings (fs:
-      (optionalString (isBindMount fs) (escape rootPrefix))
-      + (if fs.device != null then escape fs.device
+    in fstabFileSystems: { }: 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
@@ -199,9 +198,7 @@ let
       + "\n"
     ) fstabFileSystems;
 
-    initrdFstab = pkgs.writeText "initrd-fstab" (makeFstabEntries (filter utils.fsNeededForBoot fileSystems) {
-      rootPrefix = "/sysroot";
-    });
+    initrdFstab = pkgs.writeText "initrd-fstab" (makeFstabEntries (filter utils.fsNeededForBoot fileSystems) { });
 
 in
 
@@ -409,7 +406,8 @@ in
             ConditionVirtualization = "!container";
             DefaultDependencies = false; # needed to prevent a cycle
           };
-          before = [ "systemd-pstore.service" ];
+          before = [ "systemd-pstore.service" "shutdown.target" ];
+          conflicts = [ "shutdown.target" ];
           wantedBy = [ "systemd-pstore.service" ];
         };
       };
diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix
index 19ef188ce7833..fdb149a3d9a17 100644
--- a/nixos/modules/tasks/filesystems/bcachefs.nix
+++ b/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -1,28 +1,43 @@
 { config, lib, pkgs, utils, ... }:
 
-with lib;
-
 let
 
-  bootFs = filterAttrs (n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)) config.fileSystems;
-
-  mountCommand = pkgs.runCommand "mount.bcachefs" {} ''
-    mkdir -p $out/bin
-    cat > $out/bin/mount.bcachefs <<EOF
-    #!/bin/sh
-    exec "/bin/bcachefs" mount "\$@"
-    EOF
-    chmod +x $out/bin/mount.bcachefs
-  '';
+  bootFs = lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)) config.fileSystems;
 
   commonFunctions = ''
     prompt() {
         local name="$1"
         printf "enter passphrase for $name: "
     }
+
     tryUnlock() {
         local name="$1"
         local path="$2"
+        local success=false
+        local target
+        local uuid=$(echo -n $path | sed -e 's,UUID=\(.*\),\1,g')
+
+        printf "waiting for device to appear $path"
+        for try in $(seq 10); do
+          if [ -e $path ]; then
+              target=$(readlink -f $path)
+              success=true
+              break
+          else
+              target=$(blkid --uuid $uuid)
+              if [ $? == 0 ]; then
+                 success=true
+                 break
+              fi
+          fi
+          echo -n "."
+          sleep 1
+        done
+        printf "\n"
+        if [ $success == true ]; then
+            path=$target
+        fi
+
         if bcachefs unlock -c $path > /dev/null 2> /dev/null; then    # test for encryption
             prompt $name
             until bcachefs unlock $path 2> /dev/null; do              # repeat until successfully unlocked
@@ -30,55 +45,109 @@ let
                 prompt $name
             done
             printf "unlocking successful.\n"
+        else
+            echo "Cannot unlock device $uuid with path $path" >&2
         fi
     }
   '';
 
-  openCommand = name: fs:
-    let
-      # we need only unlock one device manually, and cannot pass multiple at once
-      # remove this adaptation when bcachefs implements mounting by filesystem uuid
-      # also, implement automatic waiting for the constituent devices when that happens
-      # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
-      firstDevice = head (splitString ":" fs.device);
-    in
-      ''
-        tryUnlock ${name} ${firstDevice}
+  # we need only unlock one device manually, and cannot pass multiple at once
+  # remove this adaptation when bcachefs implements mounting by filesystem uuid
+  # also, implement automatic waiting for the constituent devices when that happens
+  # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
+  firstDevice = fs: lib.head (lib.splitString ":" fs.device);
+
+  openCommand = name: fs: if config.boot.initrd.clevis.enable && (lib.hasAttr (firstDevice fs) config.boot.initrd.clevis.devices) then ''
+    if clevis decrypt < /etc/clevis/${firstDevice fs}.jwe | bcachefs unlock ${firstDevice fs}
+    then
+      printf "unlocked ${name} using clevis\n"
+    else
+      printf "falling back to interactive unlocking...\n"
+      tryUnlock ${name} ${firstDevice fs}
+    fi
+  '' else ''
+    tryUnlock ${name} ${firstDevice fs}
+  '';
+
+  mkUnits = prefix: name: fs: let
+    mountUnit = "${utils.escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint))}.mount";
+    device = firstDevice fs;
+    deviceUnit = "${utils.escapeSystemdPath device}.device";
+  in {
+    name = "unlock-bcachefs-${utils.escapeSystemdPath fs.mountPoint}";
+    value = {
+      description = "Unlock bcachefs for ${fs.mountPoint}";
+      requiredBy = [ mountUnit ];
+      after = [ deviceUnit ];
+      before = [ mountUnit "shutdown.target" ];
+      bindsTo = [ deviceUnit ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecCondition = "${pkgs.bcachefs-tools}/bin/bcachefs unlock -c \"${device}\"";
+        Restart = "on-failure";
+        RestartMode = "direct";
+        # Ideally, this service would lock the key on stop.
+        # As is, RemainAfterExit doesn't accomplish anything.
+        RemainAfterExit = true;
+      };
+      script = ''
+        ${config.boot.initrd.systemd.package}/bin/systemd-ask-password --timeout=0 "enter passphrase for ${name}" | exec ${pkgs.bcachefs-tools}/bin/bcachefs unlock "${device}"
       '';
+    };
+  };
 
+  assertions = [
+    {
+      assertion = let
+        kernel = config.boot.kernelPackages.kernel;
+      in (
+        kernel.kernelAtLeast "6.7" || (
+          lib.elem (kernel.structuredExtraConfig.BCACHEFS_FS or null) [
+            lib.kernel.module
+            lib.kernel.yes
+            (lib.kernel.option lib.kernel.yes)
+          ]
+        )
+      );
+
+      message = "Linux 6.7-rc1 at minimum or a custom linux kernel with bcachefs support is required";
+    }
+  ];
 in
 
 {
-  config = mkIf (elem "bcachefs" config.boot.supportedFilesystems) (mkMerge [
+  config = lib.mkIf (lib.elem "bcachefs" config.boot.supportedFilesystems) (lib.mkMerge [
     {
-      # We do not want to include bachefs in the fsPackages for systemd-initrd
-      # because we provide the unwrapped version of mount.bcachefs
-      # through the extraBin option, which will make it available for use.
-      system.fsPackages = lib.optional (!config.boot.initrd.systemd.enable) pkgs.bcachefs-tools;
-      environment.systemPackages = lib.optional (config.boot.initrd.systemd.enable) pkgs.bcachefs-tools;
-
-      # use kernel package with bcachefs support until it's in mainline
-      boot.kernelPackages = pkgs.linuxPackages_testing_bcachefs;
+      inherit assertions;
+      # needed for systemd-remount-fs
+      system.fsPackages = [ pkgs.bcachefs-tools ];
+      # FIXME: Remove this line when the default kernel has bcachefs
+      boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
+      systemd.services = lib.mapAttrs' (mkUnits "") (lib.filterAttrs (n: fs: (fs.fsType == "bcachefs") && (!utils.fsNeededForBoot fs)) config.fileSystems);
     }
 
-    (mkIf ((elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
+    (lib.mkIf ((lib.elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
+      inherit assertions;
       # chacha20 and poly1305 are required only for decryption attempts
       boot.initrd.availableKernelModules = [ "bcachefs" "sha256" "chacha20" "poly1305" ];
-
       boot.initrd.systemd.extraBin = {
+        # do we need this? boot/systemd.nix:566 & boot/systemd/initrd.nix:357
         "bcachefs" = "${pkgs.bcachefs-tools}/bin/bcachefs";
-        "mount.bcachefs" = "${mountCommand}/bin/mount.bcachefs";
+        "mount.bcachefs" = "${pkgs.bcachefs-tools}/bin/mount.bcachefs";
       };
-
       boot.initrd.extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
-        copy_bin_and_libs ${mountCommand}/bin/mount.bcachefs
+        copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/mount.bcachefs
       '';
-      boot.initrd.extraUtilsCommandsTest = ''
+      boot.initrd.extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         $out/bin/bcachefs version
       '';
 
-      boot.initrd.postDeviceCommands = commonFunctions + concatStrings (mapAttrsToList openCommand bootFs);
+      boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + lib.concatStrings (lib.mapAttrsToList openCommand bootFs));
+
+      boot.initrd.systemd.services = lib.mapAttrs' (mkUnits "/sysroot") bootFs;
     })
   ]);
 }
diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix
index 82fdd60587106..87fe326c09740 100644
--- a/nixos/modules/tasks/filesystems/btrfs.nix
+++ b/nixos/modules/tasks/filesystems/btrfs.nix
@@ -52,34 +52,37 @@ in
   config = mkMerge [
     (mkIf enableBtrfs {
       system.fsPackages = [ pkgs.btrfs-progs ];
+    })
 
-      boot.initrd.kernelModules = mkIf inInitrd [ "btrfs" ];
-      boot.initrd.availableKernelModules = mkIf inInitrd (
+    (mkIf inInitrd {
+      boot.initrd.kernelModules = [ "btrfs" ];
+      boot.initrd.availableKernelModules =
         [ "crc32c" ]
         ++ optionals (config.boot.kernelPackages.kernel.kernelAtLeast "5.5") [
           # Needed for mounting filesystems with new checksums
           "xxhash_generic"
           "blake2b_generic"
           "sha256_generic" # Should be baked into our kernel, just to be sure
-        ]
-      );
+        ];
 
-      boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
+      boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable)
       ''
         copy_bin_and_libs ${pkgs.btrfs-progs}/bin/btrfs
         ln -sv btrfs $out/bin/btrfsck
         ln -sv btrfsck $out/bin/fsck.btrfs
       '';
 
-      boot.initrd.extraUtilsCommandsTest = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
+      boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable)
       ''
         $out/bin/btrfs --version
       '';
 
-      boot.initrd.postDeviceCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable)
+      boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable)
       ''
         btrfs device scan
       '';
+
+      boot.initrd.systemd.initrdBin = [ pkgs.btrfs-progs ];
     })
 
     (mkIf enableAutoScrub {
diff --git a/nixos/modules/tasks/filesystems/cifs.nix b/nixos/modules/tasks/filesystems/cifs.nix
index 0de292a692082..837b9e19bfb9d 100644
--- a/nixos/modules/tasks/filesystems/cifs.nix
+++ b/nixos/modules/tasks/filesystems/cifs.nix
@@ -21,5 +21,7 @@ in
         copy_bin_and_libs ${pkgs.cifs-utils}/sbin/mount.cifs
       '';
 
+    boot.initrd.systemd.extraBin."mount.cifs" = mkIf inInitrd "${pkgs.cifs-utils}/sbin/mount.cifs";
+
   };
 }
diff --git a/nixos/modules/tasks/filesystems/ext.nix b/nixos/modules/tasks/filesystems/ext.nix
index edc0efc552136..1c34ee2c70356 100644
--- a/nixos/modules/tasks/filesystems/ext.nix
+++ b/nixos/modules/tasks/filesystems/ext.nix
@@ -25,5 +25,7 @@ in
         ln -sv e2fsck $out/bin/fsck.ext4
       '';
 
+    boot.initrd.systemd.initrdBin = lib.mkIf inInitrd [ pkgs.e2fsprogs ];
+
   };
 }
diff --git a/nixos/modules/tasks/filesystems/f2fs.nix b/nixos/modules/tasks/filesystems/f2fs.nix
index 035784f43df83..4f99f9a57fa6d 100644
--- a/nixos/modules/tasks/filesystems/f2fs.nix
+++ b/nixos/modules/tasks/filesystems/f2fs.nix
@@ -16,5 +16,7 @@ in
     boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/fsck.f2fs
     '';
+
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.f2fs-tools ];
   };
 }
diff --git a/nixos/modules/tasks/filesystems/jfs.nix b/nixos/modules/tasks/filesystems/jfs.nix
index 6d80c4c657da6..b5132b4caa334 100644
--- a/nixos/modules/tasks/filesystems/jfs.nix
+++ b/nixos/modules/tasks/filesystems/jfs.nix
@@ -15,5 +15,7 @@ in
     boot.initrd.extraUtilsCommands = mkIf (inInitrd && !config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.jfsutils}/sbin/fsck.jfs
     '';
+
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.jfsutils ];
   };
 }
diff --git a/nixos/modules/tasks/filesystems/reiserfs.nix b/nixos/modules/tasks/filesystems/reiserfs.nix
index 7b017a83db848..3c6a0f0cd917f 100644
--- a/nixos/modules/tasks/filesystems/reiserfs.nix
+++ b/nixos/modules/tasks/filesystems/reiserfs.nix
@@ -21,5 +21,7 @@ in
         ln -s reiserfsck $out/bin/fsck.reiserfs
       '';
 
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.reiserfsprogs ];
+
   };
 }
diff --git a/nixos/modules/tasks/filesystems/vfat.nix b/nixos/modules/tasks/filesystems/vfat.nix
index 5421b617b43b9..9281b34633c25 100644
--- a/nixos/modules/tasks/filesystems/vfat.nix
+++ b/nixos/modules/tasks/filesystems/vfat.nix
@@ -21,5 +21,7 @@ in
         ln -sv dosfsck $out/bin/fsck.vfat
       '';
 
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.dosfstools ];
+
   };
 }
diff --git a/nixos/modules/tasks/filesystems/xfs.nix b/nixos/modules/tasks/filesystems/xfs.nix
index f81f586465519..76f31e660ad3d 100644
--- a/nixos/modules/tasks/filesystems/xfs.nix
+++ b/nixos/modules/tasks/filesystems/xfs.nix
@@ -26,5 +26,7 @@ in
       ''
         sed -i -e 's,^#!.*,#!'$out/bin/sh, $out/bin/fsck.xfs
       '';
+
+    boot.initrd.systemd.initrdBin = mkIf inInitrd [ pkgs.xfsprogs.bin ];
   };
 }
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 21d604bee6e3f..b289d2151eb79 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -16,6 +16,10 @@ let
   cfgTrim = config.services.zfs.trim;
   cfgZED = config.services.zfs.zed;
 
+  selectModulePackage = package: config.boot.kernelPackages.${package.kernelModuleAttribute};
+  clevisDatasets = map (e: e.device) (filter (e: e.device != null && (hasAttr e.device config.boot.initrd.clevis.devices) && e.fsType == "zfs" && (fsNeededForBoot e)) config.system.build.fileSystems);
+
+
   inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
   inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
 
@@ -67,7 +71,7 @@ let
     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\" } }")"
+      state="$("${zpoolCmd}" import -d "${cfgZfs.devNodes}" 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
       if [[ "$state" = "ONLINE" ]]; then
         return 0
       else
@@ -90,21 +94,26 @@ let
 
   getPoolMounts = prefix: pool:
     let
+      poolFSes = getPoolFilesystems pool;
+
       # 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));
+
+      hasUsr = lib.any (fs: fs.mountPoint == "/usr") poolFSes;
     in
-      map (x: "${mountPoint x}.mount") (getPoolFilesystems pool);
+      map (x: "${mountPoint x}.mount") poolFSes
+      ++ lib.optional hasUsr "sysusr-usr.mount";
 
   getKeyLocations = pool: if isBool cfgZfs.requestEncryptionCredentials then {
     hasKeys = cfgZfs.requestEncryptionCredentials;
-    command = "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus ${pool}";
+    command = "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus -t volume,filesystem ${pool}";
   } else let
     keys = filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials;
   in {
     hasKeys = keys != [];
-    command = "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus ${toString keys}";
+    command = "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus -t volume,filesystem ${toString keys}";
   };
 
   createImportService = { pool, systemd, force, prefix ? "" }:
@@ -114,14 +123,15 @@ let
       # but don't *require* it, because mounts shouldn't be killed if it's stopped.
       # In the future, hopefully someone will complete this:
       # https://github.com/zfsonlinux/zfs/pull/4943
-      wants = [ "systemd-udev-settle.service" ];
+      wants = [ "systemd-udev-settle.service" ] ++ optional (config.boot.initrd.clevis.useTang) "network-online.target";
       after = [
         "systemd-udev-settle.service"
         "systemd-modules-load.service"
         "systemd-ask-password-console.service"
-      ];
+      ] ++ optional (config.boot.initrd.clevis.useTang) "network-online.target";
       requiredBy = getPoolMounts prefix pool ++ [ "zfs-import.target" ];
-      before = getPoolMounts prefix pool ++ [ "zfs-import.target" ];
+      before = getPoolMounts prefix pool ++ [ "shutdown.target" "zfs-import.target" ];
+      conflicts = [ "shutdown.target" ];
       unitConfig = {
         DefaultDependencies = "no";
       };
@@ -148,6 +158,9 @@ let
           poolImported "${pool}" || poolImport "${pool}"  # Try one last time, e.g. to import a degraded pool.
         fi
         if poolImported "${pool}"; then
+        ${optionalString config.boot.initrd.clevis.enable (concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem} || true ") (filter (p: (elemAt (splitString "/" p) 0) == pool) clevisDatasets))}
+
+
           ${optionalString keyLocations.hasKeys ''
             ${keyLocations.command} | while IFS=$'\t' read ds kl ks; do
               {
@@ -205,11 +218,17 @@ in
   options = {
     boot.zfs = {
       package = mkOption {
-        readOnly = true;
         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 = lib.mdDoc "Configured ZFS userland tools package.";
+        default = if cfgZfs.enableUnstable then pkgs.zfsUnstable else pkgs.zfs;
+        defaultText = literalExpression "if zfsUnstable is enabled then pkgs.zfsUnstable else pkgs.zfs";
+        description = lib.mdDoc "Configured ZFS userland tools package, use `pkgs.zfsUnstable` if you want to track the latest staging ZFS branch.";
+      };
+
+      modulePackage = mkOption {
+        internal = true; # It is supposed to be selected automatically, but can be overridden by expert users.
+        default = selectModulePackage cfgZfs.package;
+        type = types.package;
+        description = lib.mdDoc "Configured ZFS kernel module package.";
       };
 
       enabled = mkOption {
@@ -490,9 +509,15 @@ in
     };
 
     services.zfs.zed = {
-      enableMail = mkEnableOption (lib.mdDoc "ZED's ability to send emails") // {
-        default = cfgZfs.package.enableMail;
-        defaultText = literalExpression "config.${optZfs.package}.enableMail";
+      enableMail = mkOption {
+        type = types.bool;
+        default = config.services.mail.sendmailSetuidWrapper != null;
+        defaultText = literalExpression ''
+          config.services.mail.sendmailSetuidWrapper != null
+        '';
+        description = mdDoc ''
+          Whether to enable ZED's ability to send emails.
+        '';
       };
 
       settings = mkOption {
@@ -529,12 +554,8 @@ in
     (mkIf cfgZfs.enabled {
       assertions = [
         {
-          assertion = cfgZED.enableMail -> cfgZfs.package.enableMail;
-          message = ''
-            To allow ZED to send emails, ZFS needs to be configured to enable
-            this. To do so, one must override the `zfs` package and set
-            `enableMail` to true.
-          '';
+          assertion = cfgZfs.modulePackage.version == cfgZfs.package.version;
+          message = "The kernel module and the userspace tooling versions are not matching, this is an unsupported usecase.";
         }
         {
           assertion = config.networking.hostId != null;
@@ -566,30 +587,26 @@ in
         # https://github.com/NixOS/nixpkgs/issues/106093
         kernelParams = lib.optionals (!config.boot.zfs.allowHibernation) [ "nohibernate" ];
 
-        extraModulePackages = let
-          kernelPkg = if config.boot.zfs.enableUnstable then
-            config.boot.kernelPackages.zfsUnstable
-           else
-            config.boot.kernelPackages.zfs;
-        in [
-          (kernelPkg.override { inherit (cfgZfs) removeLinuxDRM; })
+        extraModulePackages = [
+          (cfgZfs.modulePackage.override { inherit (cfgZfs) removeLinuxDRM; })
         ];
       };
 
       boot.initrd = mkIf inInitrd {
-        kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl";
+        # spl has been removed in ≥ 2.2.0.
+        kernelModules = [ "zfs" ] ++ lib.optional (lib.versionOlder "2.2.0" version) "spl";
         extraUtilsCommands =
-          ''
+          mkIf (!config.boot.initrd.systemd.enable) ''
             copy_bin_and_libs ${cfgZfs.package}/sbin/zfs
             copy_bin_and_libs ${cfgZfs.package}/sbin/zdb
             copy_bin_and_libs ${cfgZfs.package}/sbin/zpool
           '';
-        extraUtilsCommandsTest = mkIf inInitrd
-          ''
+        extraUtilsCommandsTest =
+          mkIf (!config.boot.initrd.systemd.enable) ''
             $out/bin/zfs --help >/dev/null 2>&1
             $out/bin/zpool --help >/dev/null 2>&1
           '';
-        postDeviceCommands = concatStringsSep "\n" ([''
+        postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (concatStringsSep "\n" ([''
             ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}"
           ''] ++ [(importLib {
             # See comments at importLib definition.
@@ -611,6 +628,9 @@ in
               fi
               poolImported "${pool}" || poolImport "${pool}"  # Try one last time, e.g. to import a degraded pool.
             fi
+
+            ${optionalString config.boot.initrd.clevis.enable (concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem}") (filter (p: (elemAt (splitString "/" p) 0) == pool) clevisDatasets))}
+
             ${if isBool cfgZfs.requestEncryptionCredentials
               then optionalString cfgZfs.requestEncryptionCredentials ''
                 zfs load-key -a
@@ -618,10 +638,10 @@ in
               else concatMapStrings (fs: ''
                 zfs load-key -- ${escapeShellArg fs}
               '') (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)}
-        '') rootPools));
+        '') rootPools)));
 
         # Systemd in stage 1
-        systemd = {
+        systemd = mkIf config.boot.initrd.systemd.enable {
           packages = [cfgZfs.package];
           services = listToAttrs (map (pool: createImportService {
             inherit pool;
@@ -632,7 +652,8 @@ in
           targets.zfs-import.wantedBy = [ "zfs.target" ];
           targets.zfs.wantedBy = [ "initrd.target" ];
           extraBin = {
-            # zpool and zfs are already in thanks to fsPackages
+            zpool = "${cfgZfs.package}/sbin/zpool";
+            zfs = "${cfgZfs.package}/sbin/zfs";
             awk = "${pkgs.gawk}/bin/awk";
           };
         };
@@ -646,10 +667,17 @@ in
       # 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;
+        zfsPackage = cfgZfs.package;
       };
 
       services.zfs.zed.settings = {
-        ZED_EMAIL_PROG = mkIf cfgZED.enableMail (mkDefault "${pkgs.mailutils}/bin/mail");
+        ZED_EMAIL_PROG = mkIf cfgZED.enableMail (mkDefault (
+          config.security.wrapperDir + "/" +
+          config.services.mail.sendmailSetuidWrapper.program
+        ));
+        # subject in header for sendmail
+        ZED_EMAIL_OPTS = mkIf cfgZED.enableMail (mkDefault "@ADDRESS@");
+
         PATH = lib.makeBinPath [
           cfgZfs.package
           pkgs.coreutils
@@ -662,6 +690,11 @@ in
         ];
       };
 
+      # ZFS already has its own scheduler. Without this my(@Artturin) computer froze for a second when I nix build something.
+      services.udev.extraRules = ''
+        ACTION=="add|change", KERNEL=="sd[a-z]*[0-9]*|mmcblk[0-9]*p[0-9]*|nvme[0-9]*n[0-9]*p[0-9]*", ENV{ID_FS_TYPE}=="zfs_member", ATTR{../queue/scheduler}="none"
+      '';
+
       environment.etc = genAttrs
         (map
           (file: "zfs/zed.d/${file}")
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index 24f0c37acf908..2f2d282fbefb4 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -28,12 +28,12 @@ let
       SLAVES=$(ip link | grep 'master ${i}' | awk -F: '{print $2}')
       for I in $SLAVES; do
         UPDATED=0
-        ip link set "$I" nomaster
+        ip link set dev "$I" nomaster
       done
       [ "$UPDATED" -eq "1" ] && break
     done
-    ip link set "${i}" down 2>/dev/null || true
-    ip link del "${i}" 2>/dev/null || true
+    ip link set dev "${i}" down 2>/dev/null || true
+    ip link del dev "${i}" 2>/dev/null || true
   '';
 
   # warn that these attributes are deprecated (2017-2-2)
@@ -61,8 +61,6 @@ let
           MACAddress = i.macAddress;
         } // optionalAttrs (i.mtu != null) {
           MTUBytes = toString i.mtu;
-        } // optionalAttrs (i.wakeOnLan.enable == true) {
-          WakeOnLan = "magic";
         };
       };
     in listToAttrs (map createNetworkLink interfaces);
@@ -72,7 +70,8 @@ let
         deviceDependency = dev:
           # Use systemd service if we manage device creation, else
           # trust udev when not in a container
-          if (hasAttr dev (filterAttrs (k: v: v.virtual) cfg.interfaces)) ||
+          if (dev == null || dev == "lo") then []
+          else if (hasAttr dev (filterAttrs (k: v: v.virtual) cfg.interfaces)) ||
              (hasAttr dev cfg.bridges) ||
              (hasAttr dev cfg.bonds) ||
              (hasAttr dev cfg.macvlans) ||
@@ -80,7 +79,7 @@ let
              (hasAttr dev cfg.vlans) ||
              (hasAttr dev cfg.vswitches)
           then [ "${dev}-netdev.service" ]
-          else optional (dev != null && dev != "lo" && !config.boot.isContainer) (subsystemDevice dev);
+          else optional (!config.boot.isContainer) (subsystemDevice dev);
 
         hasDefaultGatewaySet = (cfg.defaultGateway != null && cfg.defaultGateway.address != "")
                             || (cfg.enableIPv6 && cfg.defaultGateway6 != null && cfg.defaultGateway6.address != "");
@@ -195,7 +194,7 @@ let
                 state="/run/nixos/network/addresses/${i.name}"
                 mkdir -p $(dirname "$state")
 
-                ip link set "${i.name}" up
+                ip link set dev "${i.name}" up
 
                 ${flip concatMapStrings ips (ip:
                   let
@@ -272,7 +271,7 @@ let
               ip tuntap add dev "${i.name}" mode "${i.virtualType}" user "${i.virtualOwner}"
             '';
             postStop = ''
-              ip link del ${i.name} || true
+              ip link del dev ${i.name} || true
             '';
           };
 
@@ -293,15 +292,15 @@ let
             script = ''
               # Remove Dead Interfaces
               echo "Removing old bridge ${n}..."
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link del "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link del dev "${n}"
 
               echo "Adding bridge ${n}..."
               ip link add name "${n}" type bridge
 
               # Enslave child interfaces
               ${flip concatMapStrings v.interfaces (i: ''
-                ip link set "${i}" master "${n}"
-                ip link set "${i}" up
+                ip link set dev "${i}" master "${n}"
+                ip link set dev "${i}" up
               '')}
               # Save list of enslaved interfaces
               echo "${flip concatMapStrings v.interfaces (i: ''
@@ -318,7 +317,7 @@ let
                     for uri in qemu:///system lxc:///; do
                       for dom in $(${pkgs.libvirt}/bin/virsh -c $uri list --name); do
                         ${pkgs.libvirt}/bin/virsh -c $uri dumpxml "$dom" | \
-                        ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set ',target/@dev,' master ',source/@bridge,';')" | \
+                        ${pkgs.xmlstarlet}/bin/xmlstarlet sel -t -m "//domain/devices/interface[@type='bridge'][source/@bridge='${n}'][target/@dev]" -v "concat('ip link set dev ',target/@dev,' master ',source/@bridge,';')" | \
                         ${pkgs.bash}/bin/bash
                       done
                     done
@@ -330,23 +329,23 @@ let
                 echo 2 >/sys/class/net/${n}/bridge/stp_state
               ''}
 
-              ip link set "${n}" up
+              ip link set dev "${n}" up
             '';
             postStop = ''
-              ip link set "${n}" down || true
-              ip link del "${n}" || true
+              ip link set dev "${n}" down || true
+              ip link del dev "${n}" || true
               rm -f /run/${n}.interfaces
             '';
             reload = ''
               # Un-enslave child interfaces (old list of interfaces)
               for interface in `cat /run/${n}.interfaces`; do
-                ip link set "$interface" nomaster up
+                ip link set dev "$interface" nomaster up
               done
 
               # Enslave child interfaces (new list of interfaces)
               ${flip concatMapStrings v.interfaces (i: ''
-                ip link set "${i}" master "${n}"
-                ip link set "${i}" up
+                ip link set dev "${i}" master "${n}"
+                ip link set dev "${i}" up
               '')}
               # Save list of enslaved interfaces
               echo "${flip concatMapStrings v.interfaces (i: ''
@@ -397,7 +396,7 @@ let
             postStop = ''
               echo "Cleaning Open vSwitch ${n}"
               echo "Shutting down internal ${n} interface"
-              ip link set ${n} down || true
+              ip link set dev ${n} down || true
               echo "Deleting flows for ${n}"
               ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
               echo "Deleting Open vSwitch ${n}"
@@ -435,10 +434,10 @@ let
               while [ ! -d "/sys/class/net/${n}" ]; do sleep 0.1; done;
 
               # Bring up the bond and enslave the specified interfaces
-              ip link set "${n}" up
+              ip link set dev "${n}" up
               ${flip concatMapStrings v.interfaces (i: ''
-                ip link set "${i}" down
-                ip link set "${i}" master "${n}"
+                ip link set dev "${i}" down
+                ip link set dev "${i}" master "${n}"
               '')}
             '';
             postStop = destroyBond n;
@@ -459,13 +458,13 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
               ip link add link "${v.interface}" name "${n}" type macvlan \
                 ${optionalString (v.mode != null) "mode ${v.mode}"}
-              ip link set "${n}" up
+              ip link set dev "${n}" up
             '';
             postStop = ''
-              ip link delete "${n}" || true
+              ip link delete dev "${n}" || true
             '';
           });
 
@@ -517,7 +516,7 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
               ip link add name "${n}" type sit \
                 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
                 ${optionalString (v.local != null) "local \"${v.local}\""} \
@@ -528,10 +527,10 @@ let
                     optionalString (v.encapsulation.sourcePort != null)
                       "encap-sport ${toString v.encapsulation.sourcePort}"
                   }"}
-              ip link set "${n}" up
+              ip link set dev "${n}" up
             '';
             postStop = ''
-              ip link delete "${n}" || true
+              ip link delete dev "${n}" || true
             '';
           });
 
@@ -551,16 +550,16 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
               ip link add name "${n}" type ${v.type} \
                 ${optionalString (v.remote != null) "remote \"${v.remote}\""} \
                 ${optionalString (v.local != null) "local \"${v.local}\""} \
                 ${optionalString (v.ttl != null) "${ttlarg} ${toString v.ttl}"} \
                 ${optionalString (v.dev != null) "dev \"${v.dev}\""}
-              ip link set "${n}" up
+              ip link set dev "${n}" up
             '';
             postStop = ''
-              ip link delete "${n}" || true
+              ip link delete dev "${n}" || true
             '';
           });
 
@@ -579,17 +578,17 @@ let
             path = [ pkgs.iproute2 ];
             script = ''
               # Remove Dead Interfaces
-              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete "${n}"
+              ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}"
               ip link add link "${v.interface}" name "${n}" type vlan id "${toString v.id}"
 
               # We try to bring up the logical VLAN interface. If the master
               # interface the logical interface is dependent upon is not up yet we will
               # fail to immediately bring up the logical interface. The resulting logical
               # interface will brought up later when the master interface is up.
-              ip link set "${n}" up || true
+              ip link set dev "${n}" up || true
             '';
             postStop = ''
-              ip link delete "${n}" || true
+              ip link delete dev "${n}" || true
             '';
           });
 
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index dfa883a2c3360..2009c9a7e6e28 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -28,21 +28,20 @@ let
     # TODO: warn the user that any address configured on those interfaces will be useless
     ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches);
 
-  domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
-  genericNetwork = override:
-    let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
-      ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
-        makeGateway = gateway: {
+  defaultGateways = mkMerge (forEach [ cfg.defaultGateway cfg.defaultGateway6 ] (gateway:
+    optionalAttrs (gateway != null && gateway.interface != null) {
+      networks."40-${gateway.interface}" = {
+        matchConfig.Name = gateway.interface;
+        routes = [{
           routeConfig = {
-            Gateway = gateway;
-            GatewayOnLink = false;
+            Gateway = gateway.address;
+          } // optionalAttrs (gateway.metric != null) {
+            Metric = gateway.metric;
           };
-        };
-    in optionalAttrs (gateway != [ ]) {
-      routes = override (map makeGateway gateway);
-    } // optionalAttrs (domains != [ ]) {
-      domains = override domains;
-    };
+        }];
+      };
+    }
+  ));
 
   genericDhcpNetworks = initrd: mkIf cfg.useDHCP {
     networks."99-ethernet-default-dhcp" = {
@@ -59,23 +58,14 @@ let
       # more likely to result in interfaces being configured to
       # use DHCP when they shouldn't.
 
-      # When wait-online.anyInterface is enabled, RequiredForOnline really
-      # means "sufficient for online", so we can enable it.
-      # Otherwise, don't block the network coming online because of default networks.
       matchConfig.Name = ["en*" "eth*"];
       DHCP = "yes";
-      linkConfig.RequiredForOnline =
-        lib.mkDefault (if initrd
-        then config.boot.initrd.systemd.network.wait-online.anyInterface
-        else config.systemd.network.wait-online.anyInterface);
       networkConfig.IPv6PrivacyExtensions = "kernel";
     };
     networks."99-wireless-client-dhcp" = {
       # Like above, but this is much more likely to be correct.
       matchConfig.WLANInterfaceType = "station";
       DHCP = "yes";
-      linkConfig.RequiredForOnline =
-        lib.mkDefault config.systemd.network.wait-online.anyInterface;
       networkConfig.IPv6PrivacyExtensions = "kernel";
       # We also set the route metric to one more than the default
       # of 1024, so that Ethernet is preferred if both are
@@ -98,10 +88,10 @@ let
         };
       };
     });
-    networks."40-${i.name}" = mkMerge [ (genericNetwork id) {
+    networks."40-${i.name}" = {
       name = mkDefault i.name;
       DHCP = mkForce (dhcpStr
-        (if i.useDHCP != null then i.useDHCP else false));
+        (if i.useDHCP != null then i.useDHCP else (config.networking.useDHCP && i.ipv4.addresses == [ ])));
       address = forEach (interfaceIps i)
         (ip: "${ip.address}/${toString ip.prefixLength}");
       routes = forEach (interfaceRoutes i)
@@ -170,7 +160,34 @@ let
       } // optionalAttrs (i.mtu != null) {
         MTUBytes = toString i.mtu;
       };
-    }];
+    };
+  }));
+
+  bridgeNetworks = mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
+    netdevs."40-${name}" = {
+      netdevConfig = {
+        Name = name;
+        Kind = "bridge";
+      };
+    };
+    networks = listToAttrs (forEach bridge.interfaces (bi:
+      nameValuePair "40-${bi}" {
+        DHCP = mkOverride 0 (dhcpStr false);
+        networkConfig.Bridge = name;
+      }));
+  }));
+
+  vlanNetworks = mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
+    netdevs."40-${name}" = {
+      netdevConfig = {
+        Name = name;
+        Kind = "vlan";
+      };
+      vlanConfig.Id = vlan.id;
+    };
+    networks."40-${vlan.interface}" = {
+      vlan = [ name ];
+    };
   }));
 
 in
@@ -182,7 +199,16 @@ in
     # Note this is if initrd.network.enable, not if
     # initrd.systemd.network.enable. By setting the latter and not the
     # former, the user retains full control over the configuration.
-    boot.initrd.systemd.network = mkMerge [(genericDhcpNetworks true) interfaceNetworks];
+    boot.initrd.systemd.network = mkMerge [
+      defaultGateways
+      (genericDhcpNetworks true)
+      interfaceNetworks
+      bridgeNetworks
+      vlanNetworks
+    ];
+    boot.initrd.availableKernelModules =
+      optional (cfg.bridges != {}) "bridge" ++
+      optional (cfg.vlans != {}) "8021q";
   })
 
   (mkIf cfg.useNetworkd {
@@ -191,11 +217,11 @@ in
       assertion = cfg.defaultGatewayWindowSize == null;
       message = "networking.defaultGatewayWindowSize is not supported by networkd.";
     } {
-      assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
-      message = "networking.defaultGateway.interface is not supported by networkd.";
+      assertion = cfg.defaultGateway != null -> cfg.defaultGateway.interface != null;
+      message = "networking.defaultGateway.interface is not optional when using networkd.";
     } {
-      assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
-      message = "networking.defaultGateway6.interface is not supported by networkd.";
+      assertion = cfg.defaultGateway6 != null -> cfg.defaultGateway6.interface != null;
+      message = "networking.defaultGateway6.interface is not optional when using networkd.";
     } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
       assertion = !rstp;
       message = "networking.bridges.${n}.rstp is not supported by networkd.";
@@ -210,21 +236,10 @@ in
       mkMerge [ {
         enable = true;
       }
+      defaultGateways
       (genericDhcpNetworks false)
       interfaceNetworks
-      (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
-        netdevs."40-${name}" = {
-          netdevConfig = {
-            Name = name;
-            Kind = "bridge";
-          };
-        };
-        networks = listToAttrs (forEach bridge.interfaces (bi:
-          nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
-            DHCP = mkOverride 0 (dhcpStr false);
-            networkConfig.Bridge = name;
-          } ])));
-      })))
+      bridgeNetworks
       (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
         netdevs."40-${name}" = {
           netdevConfig = {
@@ -291,10 +306,10 @@ in
         };
 
         networks = listToAttrs (forEach bond.interfaces (bi:
-          nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
+          nameValuePair "40-${bi}" {
             DHCP = mkOverride 0 (dhcpStr false);
             networkConfig.Bond = name;
-          } ])));
+          }));
       })))
       (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
         netdevs."40-${name}" = {
@@ -304,9 +319,9 @@ in
           };
           macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
         };
-        networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+        networks."40-${macvlan.interface}" = {
           macvlan = [ name ];
-        } ]);
+        };
       })))
       (mkMerge (flip mapAttrsToList cfg.fooOverUDP (name: fou: {
         netdevs."40-${name}" = {
@@ -351,9 +366,9 @@ in
               })));
         };
         networks = mkIf (sit.dev != null) {
-          "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+          "40-${sit.dev}" = {
             tunnel = [ name ];
-          } ]);
+          };
         };
       })))
       (mkMerge (flip mapAttrsToList cfg.greTunnels (name: gre: {
@@ -372,23 +387,12 @@ in
             });
         };
         networks = mkIf (gre.dev != null) {
-          "40-${gre.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
+          "40-${gre.dev}" = {
             tunnel = [ name ];
-          } ]);
-        };
-      })))
-      (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
-        netdevs."40-${name}" = {
-          netdevConfig = {
-            Name = name;
-            Kind = "vlan";
           };
-          vlanConfig.Id = vlan.id;
         };
-        networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
-          vlan = [ name ];
-        } ]);
       })))
+      vlanNetworks
     ];
 
     # We need to prefill the slaved devices with networking options
@@ -438,7 +442,7 @@ in
             postStop = ''
               echo "Cleaning Open vSwitch ${n}"
               echo "Shutting down internal ${n} interface"
-              ip link set ${n} down || true
+              ip link set dev ${n} down || true
               echo "Deleting flows for ${n}"
               ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
               echo "Deleting Open vSwitch ${n}"
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index eb1c7512d9200..ca0b219b3c93d 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -190,9 +190,11 @@ let
         type = types.nullOr types.bool;
         default = null;
         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.
+          Whether this interface should be configured with DHCP. Overrides the
+          default set by {option}`networking.useDHCP`. If `null` (the default),
+          DHCP is enabled if the interface has no IPv4 addresses configured
+          with {option}`networking.interfaces.<name>.ipv4.addresses`, and
+          disabled otherwise.
         '';
       };
 
@@ -327,6 +329,24 @@ let
           default = false;
           description = lib.mdDoc "Whether to enable wol on this interface.";
         };
+        policy = mkOption {
+          type = with types; listOf (
+            enum ["phy" "unicast" "multicast" "broadcast" "arp" "magic" "secureon"]
+          );
+          default = ["magic"];
+          description = lib.mdDoc ''
+            The [Wake-on-LAN policy](https://www.freedesktop.org/software/systemd/man/systemd.link.html#WakeOnLan=)
+            to set for the device.
+
+            The options are
+            - `phy`: Wake on PHY activity
+            - `unicast`: Wake on unicast messages
+            - `multicast`: Wake on multicast messages
+            - `broadcast`: Wake on broadcast messages
+            - `arp`: Wake on ARP
+            - `magic`: Wake on receipt of a magic packet
+          '';
+        };
       };
     };
 
@@ -478,7 +498,7 @@ in
         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
+        Modules that accept a mere `networking.hostName` but prefer a fully qualified
         domain name may use `networking.fqdnOrHostName` instead.
       '';
     };
@@ -622,9 +642,7 @@ in
           } ];
         };
       description = lib.mdDoc ''
-        The configuration for each network interface.  If
-        {option}`networking.useDHCP` is true, then every
-        interface not listed here will be configured using DHCP.
+        The configuration for each network interface.
 
         Please note that {option}`systemd.network.netdevs` has more features
         and is better maintained. When building new things, it is advised to
@@ -1286,8 +1304,8 @@ in
       default = true;
       description = lib.mdDoc ''
         Whether to use DHCP to obtain an IP address and other
-        configuration for all network interfaces that are not manually
-        configured.
+        configuration for all network interfaces that do not have any manually
+        configured IPv4 addresses.
       '';
     };
 
@@ -1326,7 +1344,10 @@ in
 
   config = {
 
-    warnings = concatMap (i: i.warnings) interfaces;
+    warnings = (concatMap (i: i.warnings) interfaces) ++ (lib.optional
+      (config.systemd.network.enable && cfg.useDHCP && !cfg.useNetworkd) ''
+        The combination of `systemd.network.enable = true`, `networking.useDHCP = true` and `networking.useNetworkd = false` can cause both networkd and dhcpcd to manage the same interfaces. This can lead to loss of networking. It is recommended you choose only one of networkd (by also enabling `networking.useNetworkd`) or scripting (by disabling `systemd.network.enable`)
+      '');
 
     assertions =
       (forEach interfaces (i: {
@@ -1375,6 +1396,8 @@ in
       "net.ipv4.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
       "net.ipv6.conf.all.disable_ipv6" = mkDefault (!cfg.enableIPv6);
       "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
+      # allow all users to do ICMP echo requests (ping)
+      "net.ipv4.ping_group_range" = mkDefault "0 2147483647";
       # networkmanager falls back to "/proc/sys/net/ipv6/conf/default/use_tempaddr"
       "net.ipv6.conf.default.use_tempaddr" = tempaddrValues.${cfg.tempAddresses}.sysctl;
     } // listToAttrs (forEach interfaces
@@ -1385,42 +1408,14 @@ in
           val = tempaddrValues.${opt}.sysctl;
          in nameValuePair "net.ipv6.conf.${replaceStrings ["."] ["/"] i.name}.use_tempaddr" val));
 
-    security.wrappers = {
-      ping = {
-        owner = "root";
-        group = "root";
-        capabilities = "cap_net_raw+p";
-        source = "${pkgs.iputils.out}/bin/ping";
-      };
+    systemd.services.domainname = lib.mkIf (cfg.domain != null) {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.ExecStart = ''${pkgs.nettools}/bin/domainname "${cfg.domain}"'';
+      serviceConfig.Type = "oneshot";
     };
-    security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter ''
-      /run/wrappers/bin/ping {
-        include <abstractions/base>
-        include <nixos/security.wrappers>
-        rpx /run/wrappers/wrappers.*/ping,
-      }
-      /run/wrappers/wrappers.*/ping {
-        include <abstractions/base>
-        include <nixos/security.wrappers>
-        r /run/wrappers/wrappers.*/ping.real,
-        mrpx ${config.security.wrappers.ping.source},
-        capability net_raw,
-        capability setpcap,
-      }
-    '');
-
-    # 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 = let
-        effectiveHostname = config.boot.kernel.sysctl."kernel.hostname" or cfg.hostName;
-      in optionalString (effectiveHostname != "") ''
-        hostname "${effectiveHostname}"
-      '';
-    system.activationScripts.domain =
-      optionalString (cfg.domain != null) ''
-        domainname "${cfg.domain}"
-      '';
 
     environment.etc.hostid = mkIf (cfg.hostId != null) { source = hostidFile; };
     boot.initrd.systemd.contents."/etc/hostid" = mkIf (cfg.hostId != null) { source = hostidFile; };
@@ -1444,15 +1439,15 @@ in
       ]
       ++ bridgeStp;
 
-    # The network-interfaces target is kept for backwards compatibility.
-    # New modules must NOT use it.
-    systemd.targets.network-interfaces =
-      { description = "All Network Interfaces (deprecated)";
-        wantedBy = [ "network.target" ];
-        before = [ "network.target" ];
-        after = [ "network-pre.target" ];
-        unitConfig.X-StopOnReconfiguration = true;
-      };
+    # Wake-on-LAN configuration is shared by the scripted and networkd backends.
+    systemd.network.links = pipe interfaces [
+      (filter (i: i.wakeOnLan.enable))
+      (map (i: nameValuePair "40-${i.name}" {
+        matchConfig.OriginalName = i.name;
+        linkConfig.WakeOnLan = concatStringsSep " " i.wakeOnLan.policy;
+      }))
+      listToAttrs
+    ];
 
     systemd.services = {
       network-local-commands = {
diff --git a/nixos/modules/tasks/swraid.nix b/nixos/modules/tasks/swraid.nix
index f624294565b06..249755bc0548c 100644
--- a/nixos/modules/tasks/swraid.nix
+++ b/nixos/modules/tasks/swraid.nix
@@ -2,6 +2,13 @@
 
   cfg = config.boot.swraid;
 
+  mdadm_conf = config.environment.etc."mdadm.conf";
+
+  enable_implicitly_for_old_state_versions = lib.versionOlder config.system.stateVersion "23.11";
+
+  minimum_config_is_set = config_text:
+    (builtins.match ".*(MAILADDR|PROGRAM).*" mdadm_conf.text) != null;
+
 in {
   imports = [
     (lib.mkRenamedOptionModule [ "boot" "initrd" "services" "swraid" "enable" ] [ "boot" "swraid" "enable" ])
@@ -24,7 +31,7 @@ in {
         should detect it correctly in the standard installation
         procedure.
       '';
-      default = lib.versionOlder config.system.stateVersion "23.11";
+      default = enable_implicitly_for_old_state_versions;
       defaultText = lib.mdDoc "`true` if stateVersion is older than 23.11";
     };
 
@@ -36,8 +43,14 @@ in {
   };
 
   config = lib.mkIf cfg.enable {
+    warnings = lib.mkIf
+        ( !enable_implicitly_for_old_state_versions && !minimum_config_is_set mdadm_conf)
+        [ "mdadm: Neither MAILADDR nor PROGRAM has been set. This will cause the `mdmon` service to crash." ];
+
     environment.systemPackages = [ pkgs.mdadm ];
 
+    environment.etc."mdadm.conf".text = lib.mkAfter cfg.mdadmConf;
+
     services.udev.packages = [ pkgs.mdadm ];
 
     systemd.packages = [ pkgs.mdadm ];
@@ -49,22 +62,20 @@ in {
         cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
       '';
 
-      extraUtilsCommands = ''
+      extraUtilsCommands = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         # Add RAID mdadm tool.
         copy_bin_and_libs ${pkgs.mdadm}/sbin/mdadm
         copy_bin_and_libs ${pkgs.mdadm}/sbin/mdmon
       '';
 
-      extraUtilsCommandsTest = ''
+      extraUtilsCommandsTest = lib.mkIf (!config.boot.initrd.systemd.enable) ''
         $out/bin/mdadm --version
       '';
 
-      extraFiles."/etc/mdadm.conf".source = pkgs.writeText "mdadm.conf" config.boot.swraid.mdadmConf;
+      extraFiles."/etc/mdadm.conf".source = pkgs.writeText "mdadm.conf" mdadm_conf.text;
 
       systemd = {
-        contents."/etc/mdadm.conf" = lib.mkIf (cfg.mdadmConf != "") {
-          text = cfg.mdadmConf;
-        };
+        contents."/etc/mdadm.conf".text = mdadm_conf.text;
 
         packages = [ pkgs.mdadm ];
         initrdBin = [ pkgs.mdadm ];
diff --git a/nixos/modules/tasks/trackpoint.nix b/nixos/modules/tasks/trackpoint.nix
index d197a0feb337c..b3f6f32eaa473 100644
--- a/nixos/modules/tasks/trackpoint.nix
+++ b/nixos/modules/tasks/trackpoint.nix
@@ -80,10 +80,17 @@ with lib;
         ACTION=="add|change", SUBSYSTEM=="input", ATTR{name}=="${cfg.device}", ATTR{device/speed}="${toString cfg.speed}", ATTR{device/sensitivity}="${toString cfg.sensitivity}"
       '';
 
-      system.activationScripts.trackpoint =
-        ''
+      systemd.services.trackpoint = {
+        wantedBy = [ "sysinit.target" ] ;
+        before = [ "sysinit.target" "shutdown.target" ];
+        conflicts = [ "shutdown.target" ];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+        serviceConfig.ExecStart = ''
           ${config.systemd.package}/bin/udevadm trigger --attr-match=name="${cfg.device}"
         '';
+      };
     })
 
     (mkIf (cfg.emulateWheel) {
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index 6dc4091bad178..6aa718c1975d7 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -6,49 +6,124 @@
 with lib;
 
 let
+  cfg = config.testing;
+
   qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; };
+
+  backdoorService = {
+    requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+    after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
+    script =
+      ''
+        export USER=root
+        export HOME=/root
+        export DISPLAY=:0.0
+
+        if [[ -e /etc/profile ]]; then
+            source /etc/profile
+        fi
+
+        # Don't use a pager when executing backdoor
+        # actions. Because we use a tty, commands like systemctl
+        # or nix-store get confused into thinking they're running
+        # interactively.
+        export PAGER=
+
+        cd /tmp
+        exec < /dev/hvc0 > /dev/hvc0
+        while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
+        echo "connecting to host..." >&2
+        stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
+        # The following line is essential since it signals to
+        # the test driver that the shell is ready.
+        # See: the connect method in the Machine class.
+        echo "Spawning backdoor root shell..."
+        # Passing the terminal device makes bash run non-interactively.
+        # Otherwise we get errors on the terminal because bash tries to
+        # setup things like job control.
+        # Note: calling bash explicitly here instead of sh makes sure that
+        # we can also run non-NixOS guests during tests.
+        PS1= exec /usr/bin/env bash --norc /dev/hvc0
+      '';
+      serviceConfig.KillSignal = "SIGHUP";
+  };
+
 in
 
 {
 
+  options.testing = {
+
+    initrdBackdoor = lib.mkEnableOption (lib.mdDoc ''
+      enable backdoor.service in initrd. Requires
+      boot.initrd.systemd.enable to be enabled. Boot will pause in
+      stage 1 at initrd.target, and will listen for commands from the
+      Machine python interface, just like stage 2 normally does. This
+      enables commands to be sent to test and debug stage 1. Use
+      machine.switch_root() to leave stage 1 and proceed to stage 2.
+    '');
+
+  };
+
   config = {
 
-    systemd.services.backdoor =
-      { wantedBy = [ "multi-user.target" ];
-        requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
-        after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ];
-        script =
-          ''
-            export USER=root
-            export HOME=/root
-            export DISPLAY=:0.0
+    assertions = [
+      {
+        assertion = cfg.initrdBackdoor -> config.boot.initrd.systemd.enable;
+        message = ''
+          testing.initrdBackdoor requires boot.initrd.systemd.enable to be enabled.
+        '';
+      }
+    ];
 
-            source /etc/profile
+    systemd.services.backdoor = lib.mkMerge [
+      backdoorService
+      {
+        wantedBy = [ "multi-user.target" ];
+      }
+    ];
 
-            # Don't use a pager when executing backdoor
-            # actions. Because we use a tty, commands like systemctl
-            # or nix-store get confused into thinking they're running
-            # interactively.
-            export PAGER=
-
-            cd /tmp
-            exec < /dev/hvc0 > /dev/hvc0
-            while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
-            echo "connecting to host..." >&2
-            stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
-            # The following line is essential since it signals to
-            # the test driver that the shell is ready.
-            # See: the connect method in the Machine class.
-            echo "Spawning backdoor root shell..."
-            # Passing the terminal device makes bash run non-interactively.
-            # Otherwise we get errors on the terminal because bash tries to
-            # setup things like job control.
-            # Note: calling bash explicitly here instead of sh makes sure that
-            # we can also run non-NixOS guests during tests.
-            PS1= exec /usr/bin/env bash --norc /dev/hvc0
-          '';
-        serviceConfig.KillSignal = "SIGHUP";
-      };
+    boot.initrd.systemd = lib.mkMerge [
+      {
+        contents."/etc/systemd/journald.conf".text = ''
+          [Journal]
+          ForwardToConsole=yes
+          MaxLevelConsole=debug
+        '';
+
+        extraConfig = config.systemd.extraConfig;
+      }
+
+      (lib.mkIf cfg.initrdBackdoor {
+        # Implemented in machine.switch_root(). Suppress the unit by
+        # making it a noop without removing it, which would break
+        # initrd-parse-etc.service
+        services.initrd-cleanup.serviceConfig.ExecStart = [
+          # Reset
+          ""
+          # noop
+          "/bin/true"
+        ];
+
+        services.backdoor = lib.mkMerge [
+          backdoorService
+          {
+            # TODO: Both stage 1 and stage 2 should use these same
+            # settings. But a lot of existing tests rely on
+            # backdoor.service having default orderings,
+            # e.g. systemd-boot.update relies on /boot being mounted
+            # as soon as backdoor starts. But it can be useful for
+            # backdoor to start even earlier.
+            wantedBy = [ "sysinit.target" ];
+            unitConfig.DefaultDependencies = false;
+            conflicts = [ "shutdown.target" "initrd-switch-root.target" ];
+            before = [ "shutdown.target" "initrd-switch-root.target" ];
+          }
+        ];
+
+        contents."/usr/bin/env".source = "${pkgs.coreutils}/bin/env";
+      })
+    ];
 
     # Prevent agetty from being instantiated on the serial device, since it
     # interferes with the backdoor (writes to it will randomly fail
@@ -104,12 +179,6 @@ in
         MaxLevelConsole=debug
       '';
 
-    boot.initrd.systemd.contents."/etc/systemd/journald.conf".text = ''
-      [Journal]
-      ForwardToConsole=yes
-      MaxLevelConsole=debug
-    '';
-
     systemd.extraConfig = ''
       # Don't clobber the console with duplicate systemd messages.
       ShowStatus=no
@@ -123,12 +192,10 @@ in
       DefaultDeviceTimeoutSec=300
     '';
 
-    boot.initrd.systemd.extraConfig = config.systemd.extraConfig;
-
     boot.consoleLogLevel = 7;
 
     # Prevent tests from accessing the Internet.
-    networking.defaultGateway = mkOverride 150 "";
+    networking.defaultGateway = mkOverride 150 null;
     networking.nameservers = mkOverride 150 [ ];
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
@@ -140,7 +207,10 @@ in
     networking.usePredictableInterfaceNames = false;
 
     # Make it easy to log in as root when running the test interactively.
-    users.users.root.initialHashedPassword = mkOverride 150 "";
+    # This needs to be a file because of a quirk in systemd credentials,
+    # where you cannot specify an empty string as a value. systemd-sysusers
+    # uses credentials to set passwords on users.
+    users.users.root.hashedPasswordFile = mkOverride 150 "${pkgs.writeText "hashed-password.root" ""}";
 
     services.xserver.displayManager.job.logToJournal = true;
 
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index aa44f26426970..f0d9b95f81f6b 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -71,6 +71,7 @@ in
 
     systemd.services.fetch-ec2-metadata = {
       wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
       after = ["network-online.target"];
       path = [ pkgs.curl ];
       script = builtins.readFile ./ec2-metadata-fetcher.sh;
diff --git a/nixos/modules/virtualisation/anbox.nix b/nixos/modules/virtualisation/anbox.nix
index c7e9e23c4c928..523d9a9576ef3 100644
--- a/nixos/modules/virtualisation/anbox.nix
+++ b/nixos/modules/virtualisation/anbox.nix
@@ -5,7 +5,7 @@ with lib;
 let
 
   cfg = config.virtualisation.anbox;
-  kernelPackages = config.boot.kernelPackages;
+
   addrOpts = v: addr: pref: name: {
     address = mkOption {
       default = addr;
@@ -25,6 +25,28 @@ let
     };
   };
 
+  finalImage = if cfg.imageModifications == "" then cfg.image else ( pkgs.callPackage (
+    { runCommandNoCC, squashfsTools }:
+
+    runCommandNoCC "${cfg.image.name}-modified.img" {
+      nativeBuildInputs = [
+        squashfsTools
+      ];
+    } ''
+      echo "-> Extracting Anbox root image..."
+      unsquashfs -dest rootfs ${cfg.image}
+
+      echo "-> Modifying Anbox root image..."
+      (
+      cd rootfs
+      ${cfg.imageModifications}
+      )
+
+      echo "-> Packing modified Anbox root image..."
+      mksquashfs rootfs $out -comp xz -no-xattrs -all-root
+    ''
+  ) { });
+
 in
 
 {
@@ -42,6 +64,18 @@ in
       '';
     };
 
+    imageModifications = mkOption {
+      default = "";
+      type = types.lines;
+      description = lib.mdDoc ''
+        Commands to edit the image filesystem.
+
+        This can be used to e.g. bundle a privileged F-Droid.
+
+        Commands are ran with PWD being at the root of the filesystem.
+      '';
+    };
+
     extraInit = mkOption {
       type = types.lines;
       default = "";
@@ -67,16 +101,19 @@ in
   config = mkIf cfg.enable {
 
     assertions = singleton {
-      assertion = versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.18";
-      message = "Anbox needs user namespace support to work properly";
+      assertion = with config.boot.kernelPackages; kernelAtLeast "5.5" && kernelOlder "5.18";
+      message = "Anbox needs a kernel with binder and ashmem support";
     };
 
     environment.systemPackages = with pkgs; [ anbox ];
 
-    services.udev.extraRules = ''
-      KERNEL=="ashmem", NAME="%k", MODE="0666"
-      KERNEL=="binder*", NAME="%k", MODE="0666"
-    '';
+    systemd.mounts = singleton {
+      requiredBy = [ "anbox-container-manager.service" ];
+      description = "Anbox Binder File System";
+      what = "binder";
+      where = "/dev/binderfs";
+      type = "binder";
+    };
 
     virtualisation.lxc.enable = true;
     networking.bridges.anbox0.interfaces = [];
@@ -87,6 +124,9 @@ in
       internalInterfaces = [ "anbox0" ];
     };
 
+    # Ensures NetworkManager doesn't touch anbox0
+    networking.networkmanager.unmanaged = [ "anbox0" ];
+
     systemd.services.anbox-container-manager = let
       anboxloc = "/var/lib/anbox";
     in {
@@ -121,12 +161,13 @@ in
         ExecStart = ''
           ${pkgs.anbox}/bin/anbox container-manager \
             --data-path=${anboxloc} \
-            --android-image=${cfg.image} \
+            --android-image=${finalImage} \
             --container-network-address=${cfg.ipv4.container.address} \
             --container-network-gateway=${cfg.ipv4.gateway.address} \
             --container-network-dns-servers=${cfg.ipv4.dns} \
             --use-rootfs-overlay \
-            --privileged
+            --privileged \
+            --daemon
         '';
       };
     };
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index 6e6021cf80fe3..ac4cd752615da 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -61,7 +61,7 @@ in
 
         # Which provisioning agent to use. Supported values are "auto" (default), "waagent",
         # "cloud-init", or "disabled".
-        Provisioning.Agent=disabled
+        Provisioning.Agent=auto
 
         # Password authentication for root account will be unavailable.
         Provisioning.DeleteRootPassword=n
@@ -202,6 +202,13 @@ in
 
     services.udev.packages = [ pkgs.waagent ];
 
+    # Provide waagent-shipped udev rules in initrd too.
+    boot.initrd.services.udev.packages = [ pkgs.waagent ];
+    # udev rules shell out to chmod, cut and readlink, which are all
+    # provided by pkgs.coreutils, which is in services.udev.path, but not
+    # boot.initrd.services.udev.binPackages.
+    boot.initrd.services.udev.binPackages = [ pkgs.coreutils ];
+
     networking.dhcpcd.persistent = true;
 
     services.logrotate = {
@@ -241,7 +248,37 @@ in
       after = [ "network-online.target" "sshd.service" ];
       wants = [ "network-online.target" ];
 
-      path = [ pkgs.e2fsprogs pkgs.bash ];
+      path = [
+        pkgs.e2fsprogs
+        pkgs.bash
+
+        pkgs.findutils
+        pkgs.gnugrep
+        pkgs.gnused
+        pkgs.iproute2
+        pkgs.iptables
+
+        # for hostname
+        pkgs.nettools
+
+        pkgs.openssh
+        pkgs.openssl
+        pkgs.parted
+
+        # for pidof
+        pkgs.procps
+
+        # for useradd, usermod
+        pkgs.shadow
+
+        pkgs.util-linux # for (u)mount, fdisk, sfdisk, mkswap
+
+        # waagent's Microsoft.OSTCExtensions.VMAccessForLinux needs Python 3
+        pkgs.python39
+
+        # waagent's Microsoft.CPlat.Core.RunCommandLinux needs lsof
+        pkgs.lsof
+      ];
       description = "Windows Azure Agent Service";
       unitConfig.ConditionPathExists = "/etc/waagent.conf";
       serviceConfig = {
@@ -250,5 +287,10 @@ in
       };
     };
 
+    # waagent will generate files under /etc/sudoers.d during provisioning
+    security.sudo.extraConfig = ''
+      #includedir /etc/sudoers.d
+    '';
+
   };
 }
diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix
index 17cfd3938305b..d909680cca1ff 100644
--- a/nixos/modules/virtualisation/azure-image.nix
+++ b/nixos/modules/virtualisation/azure-image.nix
@@ -16,6 +16,13 @@ in
         Size of disk image. Unit is MB.
       '';
     };
+    virtualisation.azureImage.contents = mkOption {
+      type = with types; listOf attrs;
+      default = [ ];
+      description = lib.mdDoc ''
+        Extra contents to add to the image.
+      '';
+    };
   };
   config = {
     system.build.azureImage = import ../../lib/make-disk-image.nix {
@@ -26,46 +33,9 @@ in
       '';
       configFile = ./azure-config-user.nix;
       format = "raw";
-      inherit (cfg) diskSize;
+      inherit (cfg) diskSize contents;
       inherit config lib pkgs;
     };
 
-    # Azure metadata is available as a CD-ROM drive.
-    fileSystems."/metadata".device = "/dev/sr0";
-
-    systemd.services.fetch-ssh-keys = {
-      description = "Fetch host keys and authorized_keys for root user";
-
-      wantedBy = [ "sshd.service" "waagent.service" ];
-      before = [ "sshd.service" "waagent.service" ];
-
-      path  = [ pkgs.coreutils ];
-      script =
-        ''
-          eval "$(cat /metadata/CustomData.bin)"
-          if ! [ -z "$ssh_host_ecdsa_key" ]; then
-            echo "downloaded ssh_host_ecdsa_key"
-            echo "$ssh_host_ecdsa_key" > /etc/ssh/ssh_host_ed25519_key
-            chmod 600 /etc/ssh/ssh_host_ed25519_key
-          fi
-
-          if ! [ -z "$ssh_host_ecdsa_key_pub" ]; then
-            echo "downloaded ssh_host_ecdsa_key_pub"
-            echo "$ssh_host_ecdsa_key_pub" > /etc/ssh/ssh_host_ed25519_key.pub
-            chmod 644 /etc/ssh/ssh_host_ed25519_key.pub
-          fi
-
-          if ! [ -z "$ssh_root_auth_key" ]; then
-            echo "downloaded ssh_root_auth_key"
-            mkdir -m 0700 -p /root/.ssh
-            echo "$ssh_root_auth_key" > /root/.ssh/authorized_keys
-            chmod 600 /root/.ssh/authorized_keys
-          fi
-        '';
-      serviceConfig.Type = "oneshot";
-      serviceConfig.RemainAfterExit = true;
-      serviceConfig.StandardError = "journal+console";
-      serviceConfig.StandardOutput = "journal+console";
-    };
   };
 }
diff --git a/nixos/modules/virtualisation/docker-rootless.nix b/nixos/modules/virtualisation/docker-rootless.nix
index f4e4bdc0963a7..1cdb98b704ce4 100644
--- a/nixos/modules/virtualisation/docker-rootless.nix
+++ b/nixos/modules/virtualisation/docker-rootless.nix
@@ -47,14 +47,7 @@ in
       '';
     };
 
-    package = mkOption {
-      default = pkgs.docker;
-      defaultText = literalExpression "pkgs.docker";
-      type = types.package;
-      description = lib.mdDoc ''
-        Docker package to be used in the module.
-      '';
-    };
+    package = mkPackageOption pkgs "docker" { };
   };
 
   ###### implementation
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index 6fe460316091b..b0d61ee06091d 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -150,14 +150,7 @@ in
       };
     };
 
-    package = mkOption {
-      default = pkgs.docker;
-      defaultText = literalExpression "pkgs.docker";
-      type = types.package;
-      description = lib.mdDoc ''
-        Docker package to be used in the module.
-      '';
-    };
+    package = mkPackageOption pkgs "docker" { };
 
     extraPackages = mkOption {
       type = types.listOf types.package;
diff --git a/nixos/modules/virtualisation/ecs-agent.nix b/nixos/modules/virtualisation/ecs-agent.nix
index dd87df9a27802..76bdccca9872b 100644
--- a/nixos/modules/virtualisation/ecs-agent.nix
+++ b/nixos/modules/virtualisation/ecs-agent.nix
@@ -8,12 +8,7 @@ in {
   options.services.ecs-agent = {
     enable = mkEnableOption (lib.mdDoc "Amazon ECS agent");
 
-    package = mkOption {
-      type = types.path;
-      description = lib.mdDoc "The ECS agent package to use";
-      default = pkgs.ecs-agent;
-      defaultText = literalExpression "pkgs.ecs-agent";
-    };
+    package = mkPackageOption pkgs "ecs-agent" { };
 
     extra-environment = mkOption {
       type = types.attrsOf types.str;
diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix
index cf94ce0faf367..887af26949feb 100644
--- a/nixos/modules/virtualisation/google-compute-config.nix
+++ b/nixos/modules/virtualisation/google-compute-config.nix
@@ -39,7 +39,7 @@ in
   # Allow root logins only using SSH keys
   # and disable password authentication in general
   services.openssh.enable = true;
-  services.openssh.settings.PermitRootLogin = "prohibit-password";
+  services.openssh.settings.PermitRootLogin = mkDefault "prohibit-password";
   services.openssh.settings.PasswordAuthentication = mkDefault false;
 
   # enable OS Login. This also requires setting enable-oslogin=TRUE metadata on
@@ -84,6 +84,10 @@ in
     { groups = [ "google-sudoers" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
   ];
 
+  security.sudo-rs.extraRules = mkIf config.users.mutableUsers [
+    { groups = [ "google-sudoers" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
+  ];
+
   users.groups.google-sudoers = mkIf config.users.mutableUsers { };
 
   boot.extraModprobeConfig = readFile "${pkgs.google-guest-configs}/etc/modprobe.d/gce-blacklist.conf";
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index 197ebb18b9ad2..e4a18fd81d715 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixos/modules/virtualisation/google-compute-image.nix
@@ -44,10 +44,22 @@ in
         GZIP compression level of the resulting disk image (1-9).
       '';
     };
+    virtualisation.googleComputeImage.efi = mkEnableOption "EFI booting";
   };
 
   #### implementation
   config = {
+    boot.initrd.availableKernelModules = [ "nvme" ];
+    boot.loader.grub = mkIf cfg.efi {
+      device = mkForce "nodev";
+      efiSupport = true;
+      efiInstallAsRemovable = true;
+    };
+
+    fileSystems."/boot" = mkIf cfg.efi {
+      device = "/dev/disk/by-label/ESP";
+      fsType = "vfat";
+    };
 
     system.build.googleComputeImage = import ../../lib/make-disk-image.nix {
       name = "google-compute-image";
@@ -62,6 +74,7 @@ in
       '';
       format = "raw";
       configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile;
+      partitionTableType = if cfg.efi then "efi" else "legacy";
       inherit (cfg) diskSize;
       inherit config lib pkgs;
     };
diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix
new file mode 100644
index 0000000000000..ea4cb916aa08d
--- /dev/null
+++ b/nixos/modules/virtualisation/incus.nix
@@ -0,0 +1,239 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.virtualisation.incus;
+  preseedFormat = pkgs.formats.yaml { };
+in
+{
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  options = {
+    virtualisation.incus = {
+      enable = lib.mkEnableOption (lib.mdDoc ''
+        incusd, a daemon that manages containers and virtual machines.
+
+        Users in the "incus-admin" group can interact with
+        the daemon (e.g. to start or stop containers) using the
+        {command}`incus` command line tool, among others.
+      '');
+
+      package = lib.mkPackageOption pkgs "incus" { };
+
+      lxcPackage = lib.mkPackageOption pkgs "lxc" { };
+
+      preseed = lib.mkOption {
+        type = lib.types.nullOr (
+          lib.types.submodule { freeformType = preseedFormat.type; }
+        );
+
+        default = null;
+
+        description = lib.mdDoc ''
+          Configuration for Incus preseed, see
+          <https://linuxcontainers.org/incus/docs/main/howto/initialize/#non-interactive-configuration>
+          for supported values.
+
+          Changes to this will be re-applied to Incus which will overwrite existing entities or create missing ones,
+          but entities will *not* be removed by preseed.
+        '';
+
+        example = {
+          networks = [
+            {
+              name = "incusbr0";
+              type = "bridge";
+              config = {
+                "ipv4.address" = "10.0.100.1/24";
+                "ipv4.nat" = "true";
+              };
+            }
+          ];
+          profiles = [
+            {
+              name = "default";
+              devices = {
+                eth0 = {
+                  name = "eth0";
+                  network = "incusbr0";
+                  type = "nic";
+                };
+                root = {
+                  path = "/";
+                  pool = "default";
+                  size = "35GiB";
+                  type = "disk";
+                };
+              };
+            }
+          ];
+          storage_pools = [
+            {
+              name = "default";
+              driver = "dir";
+              config = {
+                source = "/var/lib/incus/storage-pools/default";
+              };
+            }
+          ];
+        };
+      };
+
+      socketActivation = lib.mkEnableOption (
+        lib.mdDoc ''
+          socket-activation for starting incus.service. Enabling this option
+          will stop incus.service from starting automatically on boot.
+        ''
+      );
+
+      startTimeout = lib.mkOption {
+        type = lib.types.ints.unsigned;
+        default = 600;
+        apply = toString;
+        description = lib.mdDoc ''
+          Time to wait (in seconds) for incusd to become ready to process requests.
+          If incusd does not reply within the configured time, `incus.service` will be
+          considered failed and systemd will attempt to restart it.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # https://github.com/lxc/incus/blob/f145309929f849b9951658ad2ba3b8f10cbe69d1/doc/reference/server_settings.md
+    boot.kernel.sysctl = {
+      "fs.aio-max-nr" = lib.mkDefault 524288;
+      "fs.inotify.max_queued_events" = lib.mkDefault 1048576;
+      "fs.inotify.max_user_instances" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix
+      "fs.inotify.max_user_watches" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix
+      "kernel.dmesg_restrict" = lib.mkDefault 1;
+      "kernel.keys.maxbytes" = lib.mkDefault 2000000;
+      "kernel.keys.maxkeys" = lib.mkDefault 2000;
+      "net.core.bpf_jit_limit" = lib.mkDefault 1000000000;
+      "net.ipv4.neigh.default.gc_thresh3" = lib.mkDefault 8192;
+      "net.ipv6.neigh.default.gc_thresh3" = lib.mkDefault 8192;
+      # vm.max_map_count is set higher in nixos/modules/config/sysctl.nix
+    };
+
+    boot.kernelModules = [
+      "veth"
+      "xt_comment"
+      "xt_CHECKSUM"
+      "xt_MASQUERADE"
+      "vhost_vsock"
+    ] ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    # Note: the following options are also declared in virtualisation.lxc, but
+    # the latter can't be simply enabled to reuse the formers, because it
+    # does a bunch of unrelated things.
+    systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
+
+    security.apparmor = {
+      packages = [ cfg.lxcPackage ];
+      policies = {
+        "bin.lxc-start".profile = ''
+          include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
+        '';
+        "lxc-containers".profile = ''
+          include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
+        '';
+      };
+    };
+
+    systemd.services.incus = {
+      description = "Incus Container and Virtual Machine Management Daemon";
+
+      wantedBy = lib.mkIf (!cfg.socketActivation) [ "multi-user.target" ];
+      after = [
+        "network-online.target"
+        "lxcfs.service"
+        "incus.socket"
+      ];
+      requires = [
+        "lxcfs.service"
+        "incus.socket"
+      ];
+      wants = [
+        "network-online.target"
+      ];
+
+      path = lib.mkIf config.boot.zfs.enabled [ config.boot.zfs.package ];
+
+      environment = {
+        # Override Path to the LXC template configuration directory
+        INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config";
+      };
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/incusd --group incus-admin";
+        ExecStartPost = "${cfg.package}/bin/incusd waitready --timeout=${cfg.startTimeout}";
+        ExecStop = "${cfg.package}/bin/incus admin shutdown";
+
+        KillMode = "process"; # when stopping, leave the containers alone
+        Delegate = "yes";
+        LimitMEMLOCK = "infinity";
+        LimitNOFILE = "1048576";
+        LimitNPROC = "infinity";
+        TasksMax = "infinity";
+
+        Restart = "on-failure";
+        TimeoutStartSec = "${cfg.startTimeout}s";
+        TimeoutStopSec = "30s";
+      };
+    };
+
+    systemd.sockets.incus = {
+      description = "Incus UNIX socket";
+      wantedBy = [ "sockets.target" ];
+
+      socketConfig = {
+        ListenStream = "/var/lib/incus/unix.socket";
+        SocketMode = "0660";
+        SocketGroup = "incus-admin";
+      };
+    };
+
+    systemd.services.incus-preseed = lib.mkIf (cfg.preseed != null) {
+      description = "Incus initialization with preseed file";
+
+      wantedBy = ["incus.service"];
+      after = ["incus.service"];
+      bindsTo = ["incus.service"];
+      partOf = ["incus.service"];
+
+      script = ''
+        ${cfg.package}/bin/incus admin init --preseed <${
+          preseedFormat.generate "incus-preseed.yaml" cfg.preseed
+        }
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+    };
+
+    users.groups.incus-admin = { };
+
+    users.users.root = {
+      # match documented default ranges https://linuxcontainers.org/incus/docs/main/userns-idmap/#allowed-ranges
+      subUidRanges = [
+        {
+          startUid = 1000000;
+          count = 1000000000;
+        }
+      ];
+      subGidRanges = [
+        {
+          startGid = 1000000;
+          count = 1000000000;
+        }
+      ];
+    };
+
+    virtualisation.lxc.lxcfs.enable = true;
+  };
+}
diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index 708c577ec1edf..217242a8fbd22 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -64,25 +64,14 @@ let
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.swtpm;
-        defaultText = literalExpression "pkgs.swtpm";
-        description = lib.mdDoc ''
-          swtpm package to use.
-        '';
-      };
+      package = mkPackageOption pkgs "swtpm" { };
     };
   };
 
   qemuModule = types.submodule {
     options = {
-      package = mkOption {
-        type = types.package;
-        default = pkgs.qemu;
-        defaultText = literalExpression "pkgs.qemu";
-        description = lib.mdDoc ''
-          Qemu package to use with libvirt.
+      package = mkPackageOption pkgs "qemu" {
+        extraDescription = ''
           `pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86)
           `pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures.
         '';
@@ -127,6 +116,15 @@ let
           QEMU's swtpm options.
         '';
       };
+
+      vhostUserPackages = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        example = lib.literalExpression "[ pkgs.virtiofsd ]";
+        description = lib.mdDoc ''
+          Packages containing out-of-tree vhost-user drivers.
+        '';
+      };
     };
   };
 
@@ -183,6 +181,31 @@ let
       };
     };
   };
+
+  nssModule = types.submodule {
+    options = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          This option enables the older libvirt NSS module. This method uses
+          DHCP server records, therefore is dependent on the hostname provided
+          by the guest.
+          Please see https://libvirt.org/nss.html for more information.
+        '';
+      };
+
+      enableGuest = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc ''
+          This option enables the newer libvirt_guest NSS module. This module
+          uses the libvirt guest name instead of the hostname of the guest.
+          Please see https://libvirt.org/nss.html for more information.
+        '';
+      };
+    };
+  };
 in
 {
 
@@ -224,14 +247,7 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.libvirt;
-      defaultText = literalExpression "pkgs.libvirt";
-      description = lib.mdDoc ''
-        libvirt package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "libvirt" { };
 
     extraConfig = mkOption {
       type = types.lines;
@@ -308,6 +324,14 @@ in
         Hooks related options.
       '';
     };
+
+    nss = mkOption {
+      type = nssModule;
+      default = { };
+      description = lib.mdDoc ''
+        libvirt NSS module options.
+      '';
+    };
   };
 
 
@@ -434,7 +458,7 @@ in
         ] ++ cfg.extraOptions
       );
 
-      path = [ cfg.qemu.package ] # libvirtd requires qemu-img to manage disk images
+      path = [ cfg.qemu.package pkgs.netcat ] # libvirtd requires qemu-img to manage disk images
         ++ optional vswitch.enable vswitch.package
         ++ optional cfg.qemu.swtpm.enable cfg.qemu.swtpm.package;
 
@@ -487,6 +511,14 @@ in
     # https://libvirt.org/daemons.html#monolithic-systemd-integration
     systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ];
 
+    systemd.tmpfiles.rules = let
+      vhostUserCollection = pkgs.buildEnv {
+        name = "vhost-user";
+        paths = cfg.qemu.vhostUserPackages;
+        pathsToLink = [ "/share/qemu/vhost-user" ];
+      };
+    in [ "L+ /var/lib/qemu/vhost-user - - - - ${vhostUserCollection}/share/qemu/vhost-user" ];
+
     security.polkit = {
       enable = true;
       extraConfig = ''
@@ -498,5 +530,11 @@ in
         });
       '';
     };
+
+    system.nssModules = optional (cfg.nss.enable or cfg.nss.enableGuest) cfg.package;
+    system.nssDatabases.hosts = builtins.concatLists [
+      (optional cfg.nss.enable "libvirt")
+      (optional cfg.nss.enableGuest "libvirt_guest")
+    ];
   };
 }
diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix
index 9402d3bf37d0c..8d3a480e6dc8c 100644
--- a/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixos/modules/virtualisation/lxc-container.nix
@@ -1,25 +1,18 @@
 { lib, config, pkgs, ... }:
 
-let
-  cfg = config.virtualisation.lxc;
-in {
+{
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
   imports = [
     ./lxc-instance-common.nix
+
+    (lib.mkRemovedOptionModule [ "virtualisation" "lxc" "nestedContainer" ] "")
+    (lib.mkRemovedOptionModule [ "virtualisation" "lxc" "privilegedContainer" ] "")
   ];
 
-  options = {
-    virtualisation.lxc = {
-      privilegedContainer = lib.mkOption {
-        type = lib.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/).
-        '';
-      };
-    };
-  };
+  options = { };
 
   config = {
     boot.isContainer = true;
@@ -36,7 +29,6 @@ in {
         ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
       '';
 
-    # TODO: build rootfs as squashfs for faster unpack
     system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
       extraArgs = "--owner=0";
 
@@ -63,37 +55,32 @@ in {
       extraCommands = "mkdir -p proc sys dev";
     };
 
+    system.build.squashfs = pkgs.callPackage ../../lib/make-squashfs.nix {
+      fileName = "nixos-lxc-image-${pkgs.stdenv.hostPlatform.system}";
+
+      noStrip = true; # keep directory structure
+      comp = "zstd -Xcompression-level 6";
+
+      storeContents = [config.system.build.toplevel];
+
+      pseudoFiles = [
+        "/sbin d 0755 0 0"
+        "/sbin/init s 0555 0 0 ${config.system.build.toplevel}/init"
+        "/dev d 0755 0 0"
+        "/proc d 0555 0 0"
+        "/sys d 0555 0 0"
+      ];
+    };
+
     system.build.installBootLoader = pkgs.writeScript "install-lxd-sbin-init.sh" ''
       #!${pkgs.runtimeShell}
-      ln -fs "$1/init" /sbin/init
+      ${pkgs.coreutils}/bin/ln -fs "$1/init" /sbin/init
     '';
 
-    # Add the overrides from lxd distrobuilder
-    # https://github.com/lxc/distrobuilder/blob/05978d0d5a72718154f1525c7d043e090ba7c3e0/distrobuilder/main.go#L630
-    systemd.packages = [
-      (pkgs.writeTextFile {
-        name = "systemd-lxc-service-overrides";
-        destination = "/etc/systemd/system/service.d/zzz-lxc-service.conf";
-        text = ''
-          [Service]
-          ProcSubset=all
-          ProtectProc=default
-          ProtectControlGroups=no
-          ProtectKernelTunables=no
-          NoNewPrivileges=no
-          LoadCredential=
-        '' + lib.optionalString cfg.privilegedContainer ''
-          # Additional settings for privileged containers
-          ProtectHome=no
-          ProtectSystem=no
-          PrivateDevices=no
-          PrivateTmp=no
-          ProtectKernelLogs=no
-          ProtectKernelModules=no
-          ReadWritePaths=
-        '';
-      })
-    ];
+    # networkd depends on this, but systemd module disables this for containers
+    systemd.additionalUpstreamSystemUnits = ["systemd-udev-trigger.service"];
+
+    systemd.packages = [ pkgs.distrobuilder.generator ];
 
     system.activationScripts.installInitScript = lib.mkForce ''
       ln -fs $systemConfig/init /sbin/init
diff --git a/nixos/modules/virtualisation/lxc.nix b/nixos/modules/virtualisation/lxc.nix
index 5bd64a5f9a565..3febb4b4f2483 100644
--- a/nixos/modules/virtualisation/lxc.nix
+++ b/nixos/modules/virtualisation/lxc.nix
@@ -2,21 +2,19 @@
 
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   cfg = config.virtualisation.lxc;
-
 in
 
 {
-  ###### interface
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
 
   options.virtualisation.lxc = {
     enable =
-      mkOption {
-        type = types.bool;
+      lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description =
           lib.mdDoc ''
@@ -27,8 +25,8 @@ in
       };
 
     systemConfig =
-      mkOption {
-        type = types.lines;
+      lib.mkOption {
+        type = lib.types.lines;
         default = "";
         description =
           lib.mdDoc ''
@@ -38,8 +36,8 @@ in
       };
 
     defaultConfig =
-      mkOption {
-        type = types.lines;
+      lib.mkOption {
+        type = lib.types.lines;
         default = "";
         description =
           lib.mdDoc ''
@@ -49,8 +47,8 @@ in
       };
 
     usernetConfig =
-      mkOption {
-        type = types.lines;
+      lib.mkOption {
+        type = lib.types.lines;
         default = "";
         description =
           lib.mdDoc ''
@@ -62,7 +60,7 @@ in
 
   ###### implementation
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     environment.systemPackages = [ pkgs.lxc ];
     environment.etc."lxc/lxc.conf".text = cfg.systemConfig;
     environment.etc."lxc/lxc-usernet".text = cfg.usernetConfig;
diff --git a/nixos/modules/virtualisation/lxcfs.nix b/nixos/modules/virtualisation/lxcfs.nix
index fb0ba49f73044..b2eaec774a65c 100644
--- a/nixos/modules/virtualisation/lxcfs.nix
+++ b/nixos/modules/virtualisation/lxcfs.nix
@@ -2,18 +2,18 @@
 
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.virtualisation.lxc.lxcfs;
 in {
-  meta.maintainers = [ maintainers.mic92 ];
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
 
   ###### interface
   options.virtualisation.lxc.lxcfs = {
     enable =
-      mkOption {
-        type = types.bool;
+      lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc ''
           This enables LXCFS, a FUSE filesystem for LXC.
@@ -27,7 +27,7 @@ in {
   };
 
   ###### implementation
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     systemd.services.lxcfs = {
       description = "FUSE filesystem for LXC";
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/virtualisation/lxd-agent.nix b/nixos/modules/virtualisation/lxd-agent.nix
index 5bcc86e3bcbe9..8a2a1530eeb79 100644
--- a/nixos/modules/virtualisation/lxd-agent.nix
+++ b/nixos/modules/virtualisation/lxd-agent.nix
@@ -45,7 +45,9 @@ let
     chown -R root:root "$PREFIX"
   '';
 in {
-  meta.maintainers = with lib.maintainers; [ adamcstephens ];
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
 
   options = {
     virtualisation.lxd.agent.enable = lib.mkEnableOption (lib.mdDoc "Enable LXD agent");
@@ -56,18 +58,28 @@ in {
     systemd.services.lxd-agent = {
       enable = true;
       wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.kmod pkgs.util-linux ];
+      before = [ "shutdown.target" ] ++ lib.optionals config.services.cloud-init.enable [
+        "cloud-init.target" "cloud-init.service" "cloud-init-local.service"
+      ];
+      conflicts = [ "shutdown.target" ];
+      path = [
+        pkgs.kmod
+        pkgs.util-linux
+
+        # allow `incus exec` to find system binaries
+        "/run/current-system/sw"
+      ];
 
       preStart = preStartScript;
 
       # avoid killing nixos-rebuild switch when executed through lxc exec
+      restartIfChanged = false;
       stopIfChanged = false;
 
       unitConfig = {
         Description = "LXD - agent";
         Documentation = "https://documentation.ubuntu.com/lxd/en/latest";
         ConditionPathExists = "/dev/virtio-ports/org.linuxcontainers.lxd";
-        Before = lib.optionals config.services.cloud-init.enable [ "cloud-init.target" "cloud-init.service" "cloud-init-local.service" ];
         DefaultDependencies = "no";
         StartLimitInterval = "60";
         StartLimitBurst = "10";
diff --git a/nixos/modules/virtualisation/lxd-virtual-machine.nix b/nixos/modules/virtualisation/lxd-virtual-machine.nix
index ba729465ec2f8..92434cb9babf4 100644
--- a/nixos/modules/virtualisation/lxd-virtual-machine.nix
+++ b/nixos/modules/virtualisation/lxd-virtual-machine.nix
@@ -6,6 +6,10 @@ let
     then "ttyS0"
     else "ttyAMA0"; # aarch64
 in {
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
   imports = [
     ./lxc-instance-common.nix
 
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index 07c5e550ec58e..e0d61b1754949 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -2,21 +2,22 @@
 
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.virtualisation.lxd;
+  preseedFormat = pkgs.formats.yaml {};
 in {
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
   imports = [
-    (mkRemovedOptionModule [ "virtualisation" "lxd" "zfsPackage" ] "Override zfs in an overlay instead to override it globally")
+    (lib.mkRemovedOptionModule [ "virtualisation" "lxd" "zfsPackage" ] "Override zfs in an overlay instead to override it globally")
   ];
 
-  ###### interface
-
   options = {
     virtualisation.lxd = {
-      enable = mkOption {
-        type = types.bool;
+      enable = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc ''
           This option enables lxd, a daemon that manages
@@ -32,28 +33,18 @@ in {
         '';
       };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.lxd;
-        defaultText = literalExpression "pkgs.lxd";
-        description = lib.mdDoc ''
-          The LXD package to use.
-        '';
-      };
+      package = lib.mkPackageOption pkgs "lxd" { };
 
-      lxcPackage = mkOption {
-        type = types.package;
-        default = pkgs.lxc;
-        defaultText = literalExpression "pkgs.lxc";
-        description = lib.mdDoc ''
-          The LXC package to use with LXD (required for AppArmor profiles).
+      lxcPackage = lib.mkPackageOption pkgs "lxc" {
+        extraDescription = ''
+          Required for AppArmor profiles.
         '';
       };
 
-      zfsSupport = mkOption {
-        type = types.bool;
+      zfsSupport = lib.mkOption {
+        type = lib.types.bool;
         default = config.boot.zfs.enabled;
-        defaultText = literalExpression "config.boot.zfs.enabled";
+        defaultText = lib.literalExpression "config.boot.zfs.enabled";
         description = lib.mdDoc ''
           Enables lxd to use zfs as a storage for containers.
 
@@ -62,8 +53,8 @@ in {
         '';
       };
 
-      recommendedSysctlSettings = mkOption {
-        type = types.bool;
+      recommendedSysctlSettings = lib.mkOption {
+        type = lib.types.bool;
         default = false;
         description = lib.mdDoc ''
           Enables various settings to avoid common pitfalls when
@@ -75,8 +66,67 @@ in {
         '';
       };
 
-      startTimeout = mkOption {
-        type = types.int;
+      preseed = lib.mkOption {
+        type = lib.types.nullOr (lib.types.submodule {
+          freeformType = preseedFormat.type;
+        });
+
+        default = null;
+
+        description = lib.mdDoc ''
+          Configuration for LXD preseed, see
+          <https://documentation.ubuntu.com/lxd/en/latest/howto/initialize/#initialize-preseed>
+          for supported values.
+
+          Changes to this will be re-applied to LXD which will overwrite existing entities or create missing ones,
+          but entities will *not* be removed by preseed.
+        '';
+
+        example = lib.literalExpression ''
+          {
+            networks = [
+              {
+                name = "lxdbr0";
+                type = "bridge";
+                config = {
+                  "ipv4.address" = "10.0.100.1/24";
+                  "ipv4.nat" = "true";
+                };
+              }
+            ];
+            profiles = [
+              {
+                name = "default";
+                devices = {
+                  eth0 = {
+                    name = "eth0";
+                    network = "lxdbr0";
+                    type = "nic";
+                  };
+                  root = {
+                    path = "/";
+                    pool = "default";
+                    size = "35GiB";
+                    type = "disk";
+                  };
+                };
+              }
+            ];
+            storage_pools = [
+              {
+                name = "default";
+                driver = "dir";
+                config = {
+                  source = "/var/lib/lxd/storage-pools/default";
+                };
+              }
+            ];
+          }
+        '';
+      };
+
+      startTimeout = lib.mkOption {
+        type = lib.types.int;
         default = 600;
         apply = toString;
         description = lib.mdDoc ''
@@ -87,17 +137,15 @@ in {
       };
 
       ui = {
-        enable = lib.mkEnableOption (lib.mdDoc ''
-          Enables the (experimental) LXD UI.
-        '');
+        enable = lib.mkEnableOption (lib.mdDoc "(experimental) LXD UI");
 
-        package = mkPackageOption pkgs.lxd-unwrapped "ui" { };
+        package = lib.mkPackageOption pkgs [ "lxd-unwrapped" "ui" ] { };
       };
     };
   };
 
   ###### implementation
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     environment.systemPackages = [ cfg.package ];
 
     # Note: the following options are also declared in virtualisation.lxc, but
@@ -139,19 +187,19 @@ in {
       wantedBy = [ "multi-user.target" ];
       after = [
         "network-online.target"
-        (mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service")
+        (lib.mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service")
       ];
       requires = [
         "network-online.target"
         "lxd.socket"
-        (mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service")
+        (lib.mkIf config.virtualisation.lxc.lxcfs.enable "lxcfs.service")
       ];
       documentation = [ "man:lxd(1)" ];
 
       path = [ pkgs.util-linux ]
-        ++ optional cfg.zfsSupport config.boot.zfs.package;
+        ++ lib.optional cfg.zfsSupport config.boot.zfs.package;
 
-      environment = mkIf (cfg.ui.enable) {
+      environment = lib.mkIf (cfg.ui.enable) {
         "LXD_UI" = cfg.ui.package;
       };
 
@@ -166,16 +214,29 @@ in {
         LimitNPROC = "infinity";
         TasksMax = "infinity";
 
-        Restart = "on-failure";
-        TimeoutStartSec = "${cfg.startTimeout}s";
-        TimeoutStopSec = "30s";
-
         # By default, `lxd` loads configuration files from hard-coded
         # `/usr/share/lxc/config` - since this is a no-go for us, we have to
         # explicitly tell it where the actual configuration files are
-        Environment = mkIf (config.virtualisation.lxc.lxcfs.enable)
+        Environment = lib.mkIf (config.virtualisation.lxc.lxcfs.enable)
           "LXD_LXC_TEMPLATE_CONFIG=${pkgs.lxcfs}/share/lxc/config";
       };
+
+      unitConfig.ConditionPathExists = "!/var/lib/incus/.migrated-from-lxd";
+    };
+
+    systemd.services.lxd-preseed = lib.mkIf (cfg.preseed != null) {
+      description = "LXD initialization with preseed file";
+      wantedBy = ["multi-user.target"];
+      requires = ["lxd.service"];
+      after = ["lxd.service"];
+
+      script = ''
+        ${pkgs.coreutils}/bin/cat ${preseedFormat.generate "lxd-preseed.yaml" cfg.preseed} | ${cfg.package}/bin/lxd init --preseed
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+      };
     };
 
     users.groups.lxd = {};
@@ -185,7 +246,7 @@ in {
       subGidRanges = [ { startGid = 1000000; count = 65536; } ];
     };
 
-    boot.kernel.sysctl = mkIf cfg.recommendedSysctlSettings {
+    boot.kernel.sysctl = lib.mkIf cfg.recommendedSysctlSettings {
       "fs.inotify.max_queued_events" = 1048576;
       "fs.inotify.max_user_instances" = 1048576;
       "fs.inotify.max_user_watches" = 1048576;
@@ -197,6 +258,6 @@ in {
     };
 
     boot.kernelModules = [ "veth" "xt_comment" "xt_CHECKSUM" "xt_MASQUERADE" "vhost_vsock" ]
-      ++ optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
+      ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
   };
 }
diff --git a/nixos/modules/virtualisation/multipass.nix b/nixos/modules/virtualisation/multipass.nix
index b331b3be7ea58..5aae48e213864 100644
--- a/nixos/modules/virtualisation/multipass.nix
+++ b/nixos/modules/virtualisation/multipass.nix
@@ -22,7 +22,7 @@ in
         '';
       };
 
-      package = lib.mkPackageOptionMD pkgs "multipass" { };
+      package = lib.mkPackageOption pkgs "multipass" { };
     };
   };
 
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 5df9942dbc049..d4fa707b2dd5f 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -649,6 +649,15 @@ in
               '';
             };
 
+            restartIfChanged = mkOption {
+              type = types.bool;
+              default = true;
+              description = lib.mdDoc ''
+                Whether the container should be restarted during a NixOS
+                configuration switch if its definition has changed.
+              '';
+            };
+
             timeoutStartSec = mkOption {
               type = types.str;
               default = "1min";
@@ -745,7 +754,7 @@ in
                   { services.postgresql.enable = true;
                     services.postgresql.package = pkgs.postgresql_14;
 
-                    system.stateVersion = "21.05";
+                    system.stateVersion = "${lib.trivial.release}";
                   };
               };
           }
@@ -762,139 +771,147 @@ in
   };
 
 
-  config = mkIf (config.boot.enableContainers) (let
+  config = mkMerge [
+    {
+      warnings = optional (!config.boot.enableContainers && config.containers != {})
+        "containers.<name> is used, but boot.enableContainers is false. To use containers.<name>, set boot.enableContainers to true.";
+    }
 
-    unit = {
-      description = "Container '%i'";
+    (mkIf (config.boot.enableContainers) (let
+      unit = {
+        description = "Container '%i'";
 
-      unitConfig.RequiresMountsFor = "${stateDirectory}/%i";
+        unitConfig.RequiresMountsFor = "${stateDirectory}/%i";
 
-      path = [ pkgs.iproute2 ];
+        path = [ pkgs.iproute2 ];
 
-      environment = {
-        root = "${stateDirectory}/%i";
-        INSTANCE = "%i";
-      };
+        environment = {
+          root = "${stateDirectory}/%i";
+          INSTANCE = "%i";
+        };
 
-      preStart = preStartScript dummyConfig;
+        preStart = preStartScript dummyConfig;
 
-      script = startScript dummyConfig;
+        script = startScript dummyConfig;
 
-      postStart = postStartScript dummyConfig;
+        postStart = postStartScript dummyConfig;
 
-      restartIfChanged = false;
+        restartIfChanged = false;
 
-      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) (
-      # The generic container template used by imperative containers
-      [{ name = "container@"; value = unit; }]
-      # declarative containers
-      ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let
-          containerConfig = cfg // (
-          optionalAttrs cfg.enableTun
-            {
-              allowedDevices = cfg.allowedDevices
-                ++ [ { node = "/dev/net/tun"; modifier = "rw"; } ];
-              additionalCapabilities = cfg.additionalCapabilities
-                ++ [ "CAP_NET_ADMIN" ];
-            }
-          );
-        in
-          recursiveUpdate unit {
-            preStart = preStartScript containerConfig;
-            script = startScript containerConfig;
-            postStart = postStartScript containerConfig;
-            serviceConfig = serviceDirectives containerConfig;
-            unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "${stateDirectory}/%i";
-            environment.root = if containerConfig.ephemeral then "/run/nixos-containers/%i" else "${stateDirectory}/%i";
-          } // (
-          optionalAttrs containerConfig.autoStart
-            {
-              wantedBy = [ "machines.target" ];
-              wants = [ "network.target" ];
-              after = [ "network.target" ];
-              restartTriggers = [
-                containerConfig.path
-                config.environment.etc."${configurationDirectoryName}/${name}.conf".source
-              ];
-              restartIfChanged = true;
-            }
-          )
-      )) config.containers)
-    ));
-
-    # 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 "${configurationDirectoryName}/${name}.conf"
-      { text =
-          ''
-            SYSTEM_PATH=${cfg.path}
-            ${optionalString cfg.privateNetwork ''
-              PRIVATE_NETWORK=1
-              ${optionalString (cfg.hostBridge != null) ''
-                HOST_BRIDGE=${cfg.hostBridge}
-              ''}
-              ${optionalString (length cfg.forwardPorts > 0) ''
-                HOST_PORT=${concatStringsSep "," (map mkPortStr cfg.forwardPorts)}
-              ''}
-              ${optionalString (cfg.hostAddress != null) ''
-                HOST_ADDRESS=${cfg.hostAddress}
-              ''}
-              ${optionalString (cfg.hostAddress6 != null) ''
-                HOST_ADDRESS6=${cfg.hostAddress6}
-              ''}
-              ${optionalString (cfg.localAddress != null) ''
-                LOCAL_ADDRESS=${cfg.localAddress}
+        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) (
+        # The generic container template used by imperative containers
+        [{ name = "container@"; value = unit; }]
+        # declarative containers
+        ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let
+            containerConfig = cfg // (
+            optionalAttrs cfg.enableTun
+              {
+                allowedDevices = cfg.allowedDevices
+                  ++ [ { node = "/dev/net/tun"; modifier = "rw"; } ];
+                additionalCapabilities = cfg.additionalCapabilities
+                  ++ [ "CAP_NET_ADMIN" ];
+              }
+            );
+          in
+            recursiveUpdate unit {
+              preStart = preStartScript containerConfig;
+              script = startScript containerConfig;
+              postStart = postStartScript containerConfig;
+              serviceConfig = serviceDirectives containerConfig;
+              unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "${stateDirectory}/%i";
+              environment.root = if containerConfig.ephemeral then "/run/nixos-containers/%i" else "${stateDirectory}/%i";
+            } // (
+            optionalAttrs containerConfig.autoStart
+              {
+                wantedBy = [ "machines.target" ];
+                wants = [ "network.target" ];
+                after = [ "network.target" ];
+                restartTriggers = [
+                  containerConfig.path
+                  config.environment.etc."${configurationDirectoryName}/${name}.conf".source
+                ];
+                restartIfChanged = containerConfig.restartIfChanged;
+              }
+            )
+        )) config.containers)
+      ));
+
+      # 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 "${configurationDirectoryName}/${name}.conf"
+        { text =
+            ''
+              SYSTEM_PATH=${cfg.path}
+              ${optionalString cfg.privateNetwork ''
+                PRIVATE_NETWORK=1
+                ${optionalString (cfg.hostBridge != null) ''
+                  HOST_BRIDGE=${cfg.hostBridge}
+                ''}
+                ${optionalString (length cfg.forwardPorts > 0) ''
+                  HOST_PORT=${concatStringsSep "," (map mkPortStr cfg.forwardPorts)}
+                ''}
+                ${optionalString (cfg.hostAddress != null) ''
+                  HOST_ADDRESS=${cfg.hostAddress}
+                ''}
+                ${optionalString (cfg.hostAddress6 != null) ''
+                  HOST_ADDRESS6=${cfg.hostAddress6}
+                ''}
+                ${optionalString (cfg.localAddress != null) ''
+                  LOCAL_ADDRESS=${cfg.localAddress}
+                ''}
+                ${optionalString (cfg.localAddress6 != null) ''
+                  LOCAL_ADDRESS6=${cfg.localAddress6}
+                ''}
               ''}
-              ${optionalString (cfg.localAddress6 != null) ''
-                LOCAL_ADDRESS6=${cfg.localAddress6}
+              INTERFACES="${toString cfg.interfaces}"
+              MACVLANS="${toString cfg.macvlans}"
+              ${optionalString cfg.autoStart ''
+                AUTO_START=1
               ''}
-            ''}
-            INTERFACES="${toString cfg.interfaces}"
-            MACVLANS="${toString cfg.macvlans}"
-            ${optionalString cfg.autoStart ''
-              AUTO_START=1
-            ''}
-            EXTRA_NSPAWN_FLAGS="${mkBindFlags cfg.bindMounts +
-              optionalString (cfg.extraFlags != [])
-                (" " + concatStringsSep " " cfg.extraFlags)}"
-          '';
-      }) config.containers;
+              EXTRA_NSPAWN_FLAGS="${mkBindFlags cfg.bindMounts +
+                optionalString (cfg.extraFlags != [])
+                  (" " + concatStringsSep " " cfg.extraFlags)}"
+            '';
+        }) config.containers;
+
+      # Generate /etc/hosts entries for the containers.
+      networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null)
+        ''
+          ${head (splitString "/" cfg.localAddress)} ${name}.containers
+        '') config.containers);
 
-    # Generate /etc/hosts entries for the containers.
-    networking.extraHosts = concatStrings (mapAttrsToList (name: cfg: optionalString (cfg.localAddress != null)
-      ''
-        ${head (splitString "/" cfg.localAddress)} ${name}.containers
-      '') config.containers);
+      networking.dhcpcd.denyInterfaces = [ "ve-*" "vb-*" ];
 
-    networking.dhcpcd.denyInterfaces = [ "ve-*" "vb-*" ];
+      services.udev.extraRules = optionalString config.networking.networkmanager.enable ''
+        # Don't manage interfaces created by nixos-container.
+        ENV{INTERFACE}=="v[eb]-*", ENV{NM_UNMANAGED}="1"
+      '';
 
-    services.udev.extraRules = optionalString config.networking.networkmanager.enable ''
-      # Don't manage interfaces created by nixos-container.
-      ENV{INTERFACE}=="v[eb]-*", ENV{NM_UNMANAGED}="1"
-    '';
+      environment.systemPackages = [
+        nixos-container
+      ];
+
+      boot.kernelModules = [
+        "bridge"
+        "macvlan"
+        "tap"
+        "tun"
+      ];
+    }))
+  ];
 
-    environment.systemPackages = [
-      nixos-container
-    ];
-
-    boot.kernelModules = [
-      "bridge"
-      "macvlan"
-      "tap"
-      "tun"
-    ];
-  });
+  meta.buildDocsInSandbox = false;
 }
diff --git a/nixos/modules/virtualisation/oci-common.nix b/nixos/modules/virtualisation/oci-common.nix
new file mode 100644
index 0000000000000..a620df063151e
--- /dev/null
+++ b/nixos/modules/virtualisation/oci-common.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.oci;
+in
+{
+  imports = [ ../profiles/qemu-guest.nix ];
+
+  # Taken from /proc/cmdline of Ubuntu 20.04.2 LTS on OCI
+  boot.kernelParams = [
+    "nvme.shutdown_timeout=10"
+    "nvme_core.shutdown_timeout=10"
+    "libiscsi.debug_libiscsi_eh=1"
+    "crash_kexec_post_notifiers"
+
+    # VNC console
+    "console=tty1"
+
+    # x86_64-linux
+    "console=ttyS0"
+
+    # aarch64-linux
+    "console=ttyAMA0,115200"
+  ];
+
+  boot.growPartition = true;
+
+  fileSystems."/" = {
+    device = "/dev/disk/by-label/nixos";
+    fsType = "ext4";
+    autoResize = true;
+  };
+
+  fileSystems."/boot" = lib.mkIf cfg.efi {
+    device = "/dev/disk/by-label/ESP";
+    fsType = "vfat";
+  };
+
+  boot.loader.efi.canTouchEfiVariables = false;
+  boot.loader.grub = {
+    device = if cfg.efi then "nodev" else "/dev/sda";
+    splashImage = null;
+    extraConfig = ''
+      serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1
+      terminal_input --append serial
+      terminal_output --append serial
+    '';
+    efiInstallAsRemovable = cfg.efi;
+    efiSupport = cfg.efi;
+  };
+
+  # https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/configuringntpservice.htm#Configuring_the_Oracle_Cloud_Infrastructure_NTP_Service_for_an_Instance
+  networking.timeServers = [ "169.254.169.254" ];
+
+  services.openssh.enable = true;
+
+  # Otherwise the instance may not have a working network-online.target,
+  # making the fetch-ssh-keys.service fail
+  networking.useNetworkd = lib.mkDefault true;
+}
diff --git a/nixos/modules/virtualisation/oci-config-user.nix b/nixos/modules/virtualisation/oci-config-user.nix
new file mode 100644
index 0000000000000..70c0b34efe7a1
--- /dev/null
+++ b/nixos/modules/virtualisation/oci-config-user.nix
@@ -0,0 +1,12 @@
+{ modulesPath, ... }:
+
+{
+  # To build the configuration or use nix-env, you need to run
+  # either nixos-rebuild --upgrade or nix-channel --update
+  # to fetch the nixos channel.
+
+  # This configures everything but bootstrap services,
+  # which only need to be run once and have already finished
+  # if you are able to see this comment.
+  imports = [ "${modulesPath}/virtualisation/oci-common.nix" ];
+}
diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index a9f4ab77f866a..07ed08ab2f84d 100644
--- a/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -66,6 +66,17 @@ let
           '';
         };
 
+        labels = mkOption {
+          type = with types; attrsOf str;
+          default = {};
+          description = lib.mdDoc "Labels to attach to the container at runtime.";
+          example = literalExpression ''
+            {
+              "traefik.https.routers.example.rule" = "Host(`example.container`)";
+            }
+          '';
+        };
+
         entrypoint = mkOption {
           type = with types; nullOr str;
           description = lib.mdDoc "Override the default entrypoint of the image.";
@@ -203,6 +214,13 @@ let
           '';
         };
 
+        hostname = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = lib.mdDoc "The hostname of the container.";
+          example = "hello-world";
+        };
+
         extraOptions = mkOption {
           type = with types; listOf str;
           default = [];
@@ -228,8 +246,28 @@ let
   mkService = name: container: let
     dependsOn = map (x: "${cfg.backend}-${x}.service") container.dependsOn;
     escapedName = escapeShellArg name;
+    preStartScript = pkgs.writeShellApplication {
+      name = "pre-start";
+      runtimeInputs = [ ];
+      text = ''
+        ${cfg.backend} rm -f ${name} || true
+        ${optionalString (isValidLogin container.login) ''
+          ${cfg.backend} login \
+          ${container.login.registry} \
+          --username ${container.login.username} \
+          --password-stdin < ${container.login.passwordFile}
+        ''}
+        ${optionalString (container.imageFile != null) ''
+          ${cfg.backend} load -i ${container.imageFile}
+        ''}
+        ${optionalString (cfg.backend == "podman") ''
+          rm -f /run/podman-${escapedName}.ctr-id
+        ''}
+      '';
+    };
   in {
     wantedBy = [] ++ optional (container.autoStart) "multi-user.target";
+    wants = lib.optional (container.imageFile == null)  "network-online.target";
     after = lib.optionals (cfg.backend == "docker") [ "docker.service" "docker.socket" ]
             # if imageFile is not set, the service needs the network to download the image from the registry
             ++ lib.optionals (container.imageFile == null) [ "network-online.target" ]
@@ -242,23 +280,6 @@ let
       else if cfg.backend == "podman" then [ config.virtualisation.podman.package ]
       else throw "Unhandled backend: ${cfg.backend}";
 
-    preStart = ''
-      ${cfg.backend} rm -f ${name} || true
-      ${optionalString (isValidLogin container.login) ''
-        cat ${container.login.passwordFile} | \
-          ${cfg.backend} login \
-            ${container.login.registry} \
-            --username ${container.login.username} \
-            --password-stdin
-        ''}
-      ${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"
@@ -266,6 +287,8 @@ let
       "--log-driver=${container.log-driver}"
     ] ++ optional (container.entrypoint != null)
       "--entrypoint=${escapeShellArg container.entrypoint}"
+      ++ optional (container.hostname != null)
+      "--hostname=${escapeShellArg container.hostname}"
       ++ lib.optionals (cfg.backend == "podman") [
         "--cidfile=/run/podman-${escapedName}.ctr-id"
         "--cgroups=no-conmon"
@@ -277,6 +300,7 @@ let
       ++ map (p: "-p ${escapeShellArg p}") container.ports
       ++ optional (container.user != null) "-u ${escapeShellArg container.user}"
       ++ map (v: "-v ${escapeShellArg v}") container.volumes
+      ++ (mapAttrsToList (k: v: "-l ${escapeShellArg k}=${escapeShellArg v}") container.labels)
       ++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
       ++ map escapeShellArg container.extraOptions
       ++ [container.image]
@@ -306,7 +330,7 @@ let
       ###
       # ExecReload = ...;
       ###
-
+      ExecStartPre = [ "${preStartScript}/bin/pre-start" ];
       TimeoutStartSec = 0;
       TimeoutStopSec = 120;
       Restart = "always";
diff --git a/nixos/modules/virtualisation/oci-image.nix b/nixos/modules/virtualisation/oci-image.nix
new file mode 100644
index 0000000000000..d4af5016dd71c
--- /dev/null
+++ b/nixos/modules/virtualisation/oci-image.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.oci;
+in
+{
+  imports = [ ./oci-common.nix ];
+
+  config = {
+    system.build.OCIImage = import ../../lib/make-disk-image.nix {
+      inherit config lib pkgs;
+      name = "oci-image";
+      configFile = ./oci-config-user.nix;
+      format = "qcow2";
+      diskSize = 8192;
+      partitionTableType = if cfg.efi then "efi" else "legacy";
+    };
+
+    systemd.services.fetch-ssh-keys = {
+      description = "Fetch authorized_keys for root user";
+
+      wantedBy = [ "sshd.service" ];
+      before = [ "sshd.service" ];
+
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+
+      path  = [ pkgs.coreutils pkgs.curl ];
+      script = ''
+        mkdir -m 0700 -p /root/.ssh
+        if [ -f /root/.ssh/authorized_keys ]; then
+          echo "Authorized keys have already been downloaded"
+        else
+          echo "Downloading authorized keys from Instance Metadata Service v2"
+          curl -s -S -L \
+            -H "Authorization: Bearer Oracle" \
+            -o /root/.ssh/authorized_keys \
+            http://169.254.169.254/opc/v2/instance/metadata/ssh_authorized_keys
+          chmod 600 /root/.ssh/authorized_keys
+        fi
+      '';
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        StandardError = "journal+console";
+        StandardOutput = "journal+console";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/oci-options.nix b/nixos/modules/virtualisation/oci-options.nix
new file mode 100644
index 0000000000000..0dfedc6a530c8
--- /dev/null
+++ b/nixos/modules/virtualisation/oci-options.nix
@@ -0,0 +1,14 @@
+{ config, lib, pkgs, ... }:
+{
+  options = {
+    oci = {
+      efi = lib.mkOption {
+        default = true;
+        internal = true;
+        description = ''
+          Whether the OCI instance is using EFI.
+        '';
+      };
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix
index 32646f60f8e04..a968c732f8f7e 100644
--- a/nixos/modules/virtualisation/openvswitch.nix
+++ b/nixos/modules/virtualisation/openvswitch.nix
@@ -28,14 +28,7 @@ in {
         '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.openvswitch;
-      defaultText = literalExpression "pkgs.openvswitch";
-      description = lib.mdDoc ''
-        Open vSwitch package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "openvswitch" { };
   };
 
   config = mkIf cfg.enable (let
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index ec0b713e58b38..47382f9beab00 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -150,26 +150,33 @@ in
 
   };
 
-  config = lib.mkIf cfg.enable
-    {
+  config =
+    let
+      networkConfig = ({
+        dns_enabled = false;
+        driver = "bridge";
+        id = "0000000000000000000000000000000000000000000000000000000000000000";
+        internal = false;
+        ipam_options = { driver = "host-local"; };
+        ipv6_enabled = false;
+        name = "podman";
+        network_interface = "podman0";
+        subnets = [{ gateway = "10.88.0.1"; subnet = "10.88.0.0/16"; }];
+      } // cfg.defaultNetwork.settings);
+      inherit (networkConfig) dns_enabled network_interface;
+    in
+    lib.mkIf cfg.enable {
       environment.systemPackages = [ cfg.package ]
         ++ lib.optional cfg.dockerCompat dockerCompat;
 
       # https://github.com/containers/podman/blob/097cc6eb6dd8e598c0e8676d21267b4edb11e144/docs/tutorials/basic_networking.md#default-network
       environment.etc."containers/networks/podman.json" = lib.mkIf (cfg.defaultNetwork.settings != { }) {
-        source = json.generate "podman.json" ({
-          dns_enabled = false;
-          driver = "bridge";
-          id = "0000000000000000000000000000000000000000000000000000000000000000";
-          internal = false;
-          ipam_options = { driver = "host-local"; };
-          ipv6_enabled = false;
-          name = "podman";
-          network_interface = "podman0";
-          subnets = [{ gateway = "10.88.0.1"; subnet = "10.88.0.0/16"; }];
-        } // cfg.defaultNetwork.settings);
+        source = json.generate "podman.json" networkConfig;
       };
 
+      # containers cannot reach aardvark-dns otherwise
+      networking.firewall.interfaces.${network_interface}.allowedUDPPorts = lib.mkIf dns_enabled [ 53 ];
+
       virtualisation.containers = {
         enable = true; # Enable common /etc/containers configuration
         containersConf.settings = {
diff --git a/nixos/modules/virtualisation/qemu-guest-agent.nix b/nixos/modules/virtualisation/qemu-guest-agent.nix
index 650fb2419160f..aeab0ceac3cc2 100644
--- a/nixos/modules/virtualisation/qemu-guest-agent.nix
+++ b/nixos/modules/virtualisation/qemu-guest-agent.nix
@@ -12,12 +12,7 @@ in {
         default = false;
         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 = lib.mdDoc "The QEMU guest agent package.";
-      };
+      package = mkPackageOption pkgs [ "qemu_kvm" "ga" ] { };
   };
 
   config = mkIf cfg.enable (
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index d0a5ddd87ccf6..3d7f3ccb62f84 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -198,6 +198,39 @@ let
         fi
       ''}
 
+      ${lib.optionalString cfg.tpm.enable ''
+        NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}")
+        mkdir -p "$NIX_SWTPM_DIR"
+        ${lib.getExe cfg.tpm.package} \
+          socket \
+          --tpmstate dir="$NIX_SWTPM_DIR" \
+          --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \
+          --pid file="$NIX_SWTPM_DIR"/pid --daemon \
+          --tpm2 \
+          --log file="$NIX_SWTPM_DIR"/stdout,level=6
+
+        # Enable `fdflags` builtin in Bash
+        # We will need it to perform surgical modification of the file descriptor
+        # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor
+        # on exec.
+        # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec`
+        # at the end of this script. To work around that, we will just clear
+        # the `FD_CLOEXEC` bits as a first step.
+        enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags
+        # leave a dangling subprocess because the swtpm ctrl socket has
+        # "terminate" when the last connection disconnects, it stops swtpm.
+        # When qemu stops, or if the main shell process ends, the coproc will
+        # get signaled by virtue of the pipe between main and coproc ending.
+        # Which in turns triggers a socat connect-disconnect to swtpm which
+        # will stop it.
+        coproc waitingswtpm {
+          read || :
+          echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket
+        }
+        # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin.
+        fdflags -s-cloexec ''${waitingswtpm[1]}
+      ''}
+
       cd "$TMPDIR"
 
       ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"}
@@ -267,6 +300,7 @@ let
   };
 
   storeImage = import ../../lib/make-disk-image.nix {
+    name = "nix-store-image";
     inherit pkgs config lib;
     additionalPaths = [ regInfo ];
     format = "qcow2";
@@ -647,7 +681,7 @@ in
         import pkgs.path { system = "x86_64-darwin"; }
       '';
       description = lib.mdDoc ''
-        pkgs set to use for the host-specific packages of the vm runner.
+        Package 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.
       '';
     };
@@ -656,8 +690,8 @@ in
       package =
         mkOption {
           type = types.package;
-          default = hostPkgs.qemu_kvm;
-          defaultText = literalExpression "config.virtualisation.host.pkgs.qemu_kvm";
+          default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu;
+          defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu";
           example = literalExpression "pkgs.qemu_test";
           description = lib.mdDoc "QEMU package to use.";
         };
@@ -862,6 +896,32 @@ in
       };
     };
 
+    virtualisation.tpm = {
+      enable = mkEnableOption "a TPM device in the virtual machine with a driver, using swtpm.";
+
+      package = mkPackageOption cfg.host.pkgs "swtpm" { };
+
+      deviceModel = mkOption {
+        type = types.str;
+        default = ({
+          "i686-linux" = "tpm-tis";
+          "x86_64-linux" = "tpm-tis";
+          "ppc64-linux" = "tpm-spapr";
+          "armv7-linux" = "tpm-tis-device";
+          "aarch64-linux" = "tpm-tis-device";
+        }.${pkgs.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU"));
+        defaultText = ''
+          Based on the guest platform Linux system:
+
+          - `tpm-tis` for (i686, x86_64)
+          - `tpm-spapr` for ppc64
+          - `tpm-tis-device` for (armv7, aarch64)
+        '';
+        example = "tpm-tis-device";
+        description = lib.mdDoc "QEMU device model for the TPM, uses the appropriate default based on th guest platform system and the package passed.";
+      };
+    };
+
     virtualisation.useDefaultFilesystems =
       mkOption {
         type = types.bool;
@@ -937,7 +997,7 @@ in
               virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max.
             '';
           }
-          { assertion = cfg.directBoot.initrd != options.virtualisation.directBoot.initrd.default -> cfg.directBoot.enable;
+          { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default;
             message =
               ''
                 You changed the default of `virtualisation.directBoot.initrd` but you are not
@@ -1027,7 +1087,8 @@ in
 
     boot.initrd.availableKernelModules =
       optional cfg.writableStore "overlay"
-      ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx";
+      ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx"
+      ++ optional (cfg.tpm.enable) "tpm_tis";
 
     virtualisation.additionalPaths = [ config.system.build.toplevel ];
 
@@ -1098,6 +1159,11 @@ in
       (mkIf (!cfg.graphics) [
         "-nographic"
       ])
+      (mkIf (cfg.tpm.enable) [
+        "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket"
+        "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm"
+        "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0"
+      ])
     ];
 
     virtualisation.qemu.drives = mkMerge [
@@ -1121,11 +1187,12 @@ in
       }) cfg.emptyDiskImages)
     ];
 
-    # Use mkVMOverride to enable building test VMs (e.g. via `nixos-rebuild
-    # build-vm`) of a system configuration, where the regular value for the
-    # `fileSystems' attribute should be disregarded (since those filesystems
-    # don't necessarily exist in the VM).
-    fileSystems = mkVMOverride cfg.fileSystems;
+    # By default, use mkVMOverride to enable building test VMs (e.g. via
+    # `nixos-rebuild build-vm`) of a system configuration, where the regular
+    # value for the `fileSystems' attribute should be disregarded (since those
+    # filesystems don't necessarily exist in the VM). You can disable this
+    # override by setting `virtualisation.fileSystems = lib.mkForce { };`.
+    fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems);
 
     virtualisation.fileSystems = let
       mkSharedDir = tag: share:
@@ -1189,6 +1256,8 @@ in
         unitConfig.RequiresMountsFor = "/sysroot/nix/.ro-store";
       }];
       services.rw-store = {
+        before = [ "shutdown.target" ];
+        conflicts = [ "shutdown.target" ];
         unitConfig = {
           DefaultDependencies = false;
           RequiresMountsFor = "/sysroot/nix/.rw-store";
diff --git a/nixos/modules/virtualisation/vagrant-guest.nix b/nixos/modules/virtualisation/vagrant-guest.nix
index 263b1ebca0868..2fad376086e34 100644
--- a/nixos/modules/virtualisation/vagrant-guest.nix
+++ b/nixos/modules/virtualisation/vagrant-guest.nix
@@ -55,4 +55,5 @@ in
   };
 
   security.sudo.wheelNeedsPassword = false;
+  security.sudo-rs.wheelNeedsPassword = false;
 }
diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix
index b1565a09682a1..50a8f8189590e 100644
--- a/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/nixos/modules/virtualisation/virtualbox-host.nix
@@ -40,14 +40,7 @@ in
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.virtualbox;
-      defaultText = literalExpression "pkgs.virtualbox";
-      description = lib.mdDoc ''
-        Which VirtualBox package to use.
-      '';
-    };
+    package = mkPackageOption pkgs "virtualbox" { };
 
     addNetworkInterface = mkOption {
       type = types.bool;
@@ -91,7 +84,7 @@ in
   };
 
   config = mkIf cfg.enable (mkMerge [{
-    warnings = mkIf (config.nixpkgs.config.virtualbox.enableExtensionPack or false)
+    warnings = mkIf (pkgs.config.virtualbox.enableExtensionPack or false)
       ["'nixpkgs.virtualbox.enableExtensionPack' has no effect, please use 'virtualisation.virtualbox.host.enableExtensionPack'"];
     boot.kernelModules = [ "vboxdrv" "vboxnetadp" "vboxnetflt" ];
     boot.extraModulePackages = [ kernelModules ];
diff --git a/nixos/modules/virtualisation/vmware-host.nix b/nixos/modules/virtualisation/vmware-host.nix
index 4b2dc28aeac71..094114623a424 100644
--- a/nixos/modules/virtualisation/vmware-host.nix
+++ b/nixos/modules/virtualisation/vmware-host.nix
@@ -37,12 +37,7 @@ in
           :::
         '';
       };
-      package = mkOption {
-        type = types.package;
-        default = pkgs.vmware-workstation;
-        defaultText = literalExpression "pkgs.vmware-workstation";
-        description = lib.mdDoc "VMware host virtualisation package to use";
-      };
+      package = mkPackageOption pkgs "vmware-workstation" { };
       extraPackages = mkOption {
         type = with types; listOf package;
         default = with pkgs; [ ];
@@ -90,34 +85,43 @@ in
       };
     };
 
-    ###### wrappers activation script
+    # Services
 
-    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}"
+    systemd.services."vmware-wrappers" = {
+      description = "Create VMVare Wrappers";
+      wantedBy = [ "multi-user.target" ];
+      before = [
+        "vmware-authdlauncher.service"
+        "vmware-networks-configuration.service"
+        "vmware-networks.service"
+        "vmware-usbarbitrator.service"
+      ];
+      after = [ "systemd-sysusers.service" ];
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      script = ''
+        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
-        '';
-
-    # Services
+          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
+      '';
+    };
 
     systemd.services."vmware-authdlauncher" = {
       description = "VMware Authentication Daemon";
diff --git a/nixos/modules/virtualisation/waydroid.nix b/nixos/modules/virtualisation/waydroid.nix
index 46e5f901015d9..1f466c780cf22 100644
--- a/nixos/modules/virtualisation/waydroid.nix
+++ b/nixos/modules/virtualisation/waydroid.nix
@@ -1,10 +1,8 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   cfg = config.virtualisation.waydroid;
+  kCfg = config.lib.kernelConfig;
   kernelPackages = config.boot.kernelPackages;
   waydroidGbinderConf = pkgs.writeText "waydroid.conf" ''
     [Protocol]
@@ -22,19 +20,19 @@ in
 {
 
   options.virtualisation.waydroid = {
-    enable = mkEnableOption (lib.mdDoc "Waydroid");
+    enable = lib.mkEnableOption (lib.mdDoc "Waydroid");
   };
 
-  config = mkIf cfg.enable {
-    assertions = singleton {
-      assertion = versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.18";
+  config = lib.mkIf cfg.enable {
+    assertions = lib.singleton {
+      assertion = lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.18";
       message = "Waydroid needs user namespace support to work properly";
     };
 
-    system.requiredKernelConfig = with config.lib.kernelConfig; [
-      (isEnabled "ANDROID_BINDER_IPC")
-      (isEnabled "ANDROID_BINDERFS")
-      (isEnabled "ASHMEM") # FIXME Needs memfd support instead on Linux 5.18 and waydroid 1.2.1
+    system.requiredKernelConfig = [
+      (kCfg.isEnabled "ANDROID_BINDER_IPC")
+      (kCfg.isEnabled "ANDROID_BINDERFS")
+      (kCfg.isEnabled "MEMFD_CREATE")
     ];
 
     /* NOTE: we always enable this flag even if CONFIG_PSI_DEFAULT_DISABLED is not on
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index 29dcdab7d18e5..7700441b1d6be 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -5,7 +5,7 @@
 { nixpkgs ? { outPath = (import ../lib).cleanSource ./..; revCount = 56789; shortRev = "gfedcba"; }
 , stableBranch ? false
 , supportedSystems ? [ "aarch64-linux" "x86_64-linux" ]
-, limitedSupportedSystems ? [ "i686-linux" ]
+, limitedSupportedSystems ? [ ]
 }:
 
 let
@@ -67,10 +67,19 @@ in rec {
         (onSystems ["x86_64-linux"] "nixos.tests.docker")
         (onFullSupported "nixos.tests.ecryptfs")
         (onFullSupported "nixos.tests.env")
-        (onFullSupported "nixos.tests.firefox-esr")
-        (onFullSupported "nixos.tests.firefox")
+
+        # Way too many manual retries required on Hydra.
+        #  Apparently it's hard to track down the cause.
+        #  So let's depend just on the packages for now.
+        #(onFullSupported "nixos.tests.firefox-esr")
+        #(onFullSupported "nixos.tests.firefox")
+        # Note: only -unwrapped variants have a Hydra job.
+        (onFullSupported "nixpkgs.firefox-esr-unwrapped")
+        (onFullSupported "nixpkgs.firefox-unwrapped")
+
         (onFullSupported "nixos.tests.firewall")
         (onFullSupported "nixos.tests.fontconfig-default-fonts")
+        (onFullSupported "nixos.tests.gitlab")
         (onFullSupported "nixos.tests.gnome")
         (onFullSupported "nixos.tests.gnome-xorg")
         (onSystems ["x86_64-linux"] "nixos.tests.hibernate")
@@ -81,6 +90,7 @@ in rec {
         (onSystems ["x86_64-linux"] "nixos.tests.installer.btrfsSubvols")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.luksroot")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.lvm")
+        (onSystems ["x86_64-linux"] "nixos.tests.installer.separateBootZfs")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.separateBootFat")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.separateBoot")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.simpleLabels")
@@ -88,6 +98,8 @@ in rec {
         (onSystems ["x86_64-linux"] "nixos.tests.installer.simpleUefiSystemdBoot")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.simple")
         (onSystems ["x86_64-linux"] "nixos.tests.installer.swraid")
+        (onSystems ["x86_64-linux"] "nixos.tests.installer.zfsroot")
+        (onSystems ["x86_64-linux"] "nixos.tests.nixos-rebuild-specialisations")
         (onFullSupported "nixos.tests.ipv6")
         (onFullSupported "nixos.tests.keymap.azerty")
         (onFullSupported "nixos.tests.keymap.colemak")
@@ -154,13 +166,13 @@ in rec {
         (onFullSupported "nixos.tests.switchTest")
         (onFullSupported "nixos.tests.udisks2")
         (onFullSupported "nixos.tests.xfce")
-        (onSystems ["i686-linux"] "nixos.tests.zfs.installer")
         (onFullSupported "nixpkgs.emacs")
         (onFullSupported "nixpkgs.jdk")
+        (onSystems ["x86_64-linux"] "nixpkgs.mesa_i686") # i686 sanity check + useful
         ["nixpkgs.tarball"]
 
-        # Ensure that nixpkgs-check-by-name is available in all release channels and nixos-unstable,
-        # so that a pre-built version can be used in CI for PR's on the corresponding development branches.
+        # Ensure that nixpkgs-check-by-name is available in nixos-unstable,
+        # so that a pre-built version can be used in CI for PR's
         # See ../pkgs/test/nixpkgs-check-by-name/README.md
         (onSystems ["x86_64-linux"] "nixpkgs.tests.nixpkgs-check-by-name")
       ];
diff --git a/nixos/release.nix b/nixos/release.nix
index abaa7ef9a7113..2acc5ade7848b 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -123,7 +123,7 @@ let
       build = configEvaled.config.system.build;
       kernelTarget = configEvaled.pkgs.stdenv.hostPlatform.linux-kernel.target;
     in
-      pkgs.symlinkJoin {
+      configEvaled.pkgs.symlinkJoin {
         name = "netboot";
         paths = [
           build.netbootRamdisk
@@ -328,6 +328,21 @@ in rec {
 
   );
 
+  lxdContainerImageSquashfs = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
+
+    with import ./.. { inherit system; };
+
+    hydraJob ((import lib/eval-config.nix {
+      inherit system;
+      modules =
+        [ configuration
+          versionModule
+          ./maintainers/scripts/lxd/lxd-container-image.nix
+        ];
+    }).config.system.build.squashfs)
+
+  );
+
   # Metadata for the lxd image
   lxdContainerMeta = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
 
@@ -383,7 +398,7 @@ in rec {
         modules = singleton ({ ... }:
           { fileSystems."/".device  = mkDefault "/dev/sda1";
             boot.loader.grub.device = mkDefault "/dev/sda";
-            system.stateVersion = mkDefault "18.03";
+            system.stateVersion = mkDefault lib.trivial.release;
           });
       }).config.system.build.toplevel;
       preferLocalBuild = true;
diff --git a/nixos/tests/3proxy.nix b/nixos/tests/3proxy.nix
index 83d39de018a39..b80b4e166d481 100644
--- a/nixos/tests/3proxy.nix
+++ b/nixos/tests/3proxy.nix
@@ -134,6 +134,7 @@
   testScript = ''
     start_all()
 
+    peer0.systemctl("start network-online.target")
     peer0.wait_for_unit("network-online.target")
 
     peer1.wait_for_unit("3proxy.service")
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
index 4d220b9747aa6..272782dc2f621 100644
--- a/nixos/tests/acme.nix
+++ b/nixos/tests/acme.nix
@@ -18,7 +18,7 @@
   dnsConfig = nodes: {
     dnsProvider = "exec";
     dnsPropagationCheck = false;
-    credentialsFile = pkgs.writeText "wildcard.env" ''
+    environmentFile = pkgs.writeText "wildcard.env" ''
       EXEC_PATH=${dnsScript nodes}
       EXEC_POLLING_INTERVAL=1
       EXEC_PROPAGATION_TIMEOUT=1
@@ -266,6 +266,37 @@ in {
           }
         ];
 
+        concurrency-limit.configuration = {pkgs, ...}: lib.mkMerge [
+          webserverBasicConfig {
+            security.acme.maxConcurrentRenewals = 1;
+
+            services.nginx.virtualHosts = {
+              "f.example.test" = vhostBase // {
+                enableACME = true;
+              };
+              "g.example.test" = vhostBase // {
+                enableACME = true;
+              };
+              "h.example.test" = vhostBase // {
+                enableACME = true;
+              };
+            };
+
+            systemd.services = {
+              # check for mutual exclusion of starting renew services
+              "acme-f.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-f" ''
+                test "$(systemctl is-active acme-{g,h}.example.test.service | grep activating | wc -l)" -le 0
+                '');
+              "acme-g.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-g" ''
+                test "$(systemctl is-active acme-{f,h}.example.test.service | grep activating | wc -l)" -le 0
+                '');
+              "acme-h.example.test".serviceConfig.ExecPreStart = "+" + (pkgs.writeShellScript "test-h" ''
+                test "$(systemctl is-active acme-{g,f}.example.test.service | grep activating | wc -l)" -le 0
+                '');
+              };
+          }
+        ];
+
         # Test lego internal server (listenHTTP option)
         # Also tests useRoot option
         lego-server.configuration = { ... }: {
@@ -297,7 +328,7 @@ in {
 
           services.caddy = {
             enable = true;
-            virtualHosts."a.exmaple.test" = {
+            virtualHosts."a.example.test" = {
               useACMEHost = "example.test";
               extraConfig = ''
                 root * ${documentRoot}
@@ -491,6 +522,7 @@ in {
           'curl --data \'{"host": "${caDomain}", "addresses": ["${nodes.acme.networking.primaryIPAddress}"]}\' http://${dnsServerIP nodes}:8055/add-a'
       )
 
+      acme.systemctl("start network-online.target")
       acme.wait_for_unit("network-online.target")
       acme.wait_for_unit("pebble.service")
 
@@ -591,6 +623,15 @@ in {
           webserver.wait_for_unit("nginx.service")
           check_connection(client, "slow.example.test")
 
+      with subtest("Can limit concurrency of running renewals"):
+          switch_to(webserver, "concurrency-limit")
+          webserver.wait_for_unit("acme-finished-f.example.test.target")
+          webserver.wait_for_unit("acme-finished-g.example.test.target")
+          webserver.wait_for_unit("acme-finished-h.example.test.target")
+          check_connection(client, "f.example.test")
+          check_connection(client, "g.example.test")
+          check_connection(client, "h.example.test")
+
       with subtest("Works with caddy"):
           switch_to(webserver, "caddy")
           webserver.wait_for_unit("acme-finished-example.test.target")
diff --git a/nixos/tests/activation/etc-overlay-immutable.nix b/nixos/tests/activation/etc-overlay-immutable.nix
new file mode 100644
index 0000000000000..70c3623b929c5
--- /dev/null
+++ b/nixos/tests/activation/etc-overlay-immutable.nix
@@ -0,0 +1,30 @@
+{ lib, ... }: {
+
+  name = "activation-etc-overlay-immutable";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { pkgs, ... }: {
+    system.etc.overlay.enable = true;
+    system.etc.overlay.mutable = false;
+
+    # Prerequisites
+    systemd.sysusers.enable = true;
+    users.mutableUsers = false;
+    boot.initrd.systemd.enable = true;
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    specialisation.new-generation.configuration = {
+      environment.etc."newgen".text = "newgen";
+    };
+  };
+
+  testScript = ''
+    machine.succeed("findmnt --kernel --type overlay /etc")
+    machine.fail("stat /etc/newgen")
+
+    machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+    assert machine.succeed("cat /etc/newgen") == "newgen"
+  '';
+}
diff --git a/nixos/tests/activation/etc-overlay-mutable.nix b/nixos/tests/activation/etc-overlay-mutable.nix
new file mode 100644
index 0000000000000..cfe7604fceb84
--- /dev/null
+++ b/nixos/tests/activation/etc-overlay-mutable.nix
@@ -0,0 +1,30 @@
+{ lib, ... }: {
+
+  name = "activation-etc-overlay-mutable";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { pkgs, ... }: {
+    system.etc.overlay.enable = true;
+    system.etc.overlay.mutable = true;
+
+    # Prerequisites
+    boot.initrd.systemd.enable = true;
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    specialisation.new-generation.configuration = {
+      environment.etc."newgen".text = "newgen";
+    };
+  };
+
+  testScript = ''
+    machine.succeed("findmnt --kernel --type overlay /etc")
+    machine.fail("stat /etc/newgen")
+    machine.succeed("echo -n 'mutable' > /etc/mutable")
+
+    machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+    assert machine.succeed("cat /etc/newgen") == "newgen"
+    assert machine.succeed("cat /etc/mutable") == "mutable"
+  '';
+}
diff --git a/nixos/tests/activation/nix-channel.nix b/nixos/tests/activation/nix-channel.nix
new file mode 100644
index 0000000000000..d26ea98e56cc5
--- /dev/null
+++ b/nixos/tests/activation/nix-channel.nix
@@ -0,0 +1,26 @@
+{ lib, ... }:
+
+{
+
+  name = "activation-nix-channel";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = {
+    nix.channel.enable = true;
+  };
+
+  testScript = { nodes, ... }: ''
+    machine.start(allow_reboot=True)
+
+    assert machine.succeed("cat /root/.nix-channels") == "${nodes.machine.system.defaultChannel} nixos\n"
+
+    nixpkgs_unstable_channel = "https://nixos.org/channels/nixpkgs-unstable nixpkgs"
+    machine.succeed(f"echo '{nixpkgs_unstable_channel}' > /root/.nix-channels")
+
+    machine.reboot()
+
+    assert machine.succeed("cat /root/.nix-channels") == f"{nixpkgs_unstable_channel}\n"
+  '';
+
+}
diff --git a/nixos/tests/activation/perlless.nix b/nixos/tests/activation/perlless.nix
new file mode 100644
index 0000000000000..4d784b4542f45
--- /dev/null
+++ b/nixos/tests/activation/perlless.nix
@@ -0,0 +1,24 @@
+{ lib, ... }:
+
+{
+
+  name = "activation-perlless";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { pkgs, modulesPath, ... }: {
+    imports = [ "${modulesPath}/profiles/perlless.nix" ];
+
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    virtualisation.mountHostNixStore = false;
+    virtualisation.useNixStoreImage = true;
+  };
+
+  testScript = ''
+    perl_store_paths = machine.succeed("ls /nix/store | grep perl || true")
+    print(perl_store_paths)
+    assert len(perl_store_paths) == 0
+  '';
+
+}
diff --git a/nixos/tests/activation/var.nix b/nixos/tests/activation/var.nix
new file mode 100644
index 0000000000000..1a546a7671c54
--- /dev/null
+++ b/nixos/tests/activation/var.nix
@@ -0,0 +1,18 @@
+{ lib, ... }:
+
+{
+
+  name = "activation-var";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { };
+
+  testScript = ''
+    assert machine.succeed("stat -c '%a' /var/tmp") == "1777\n"
+    assert machine.succeed("stat -c '%a' /var/empty") == "555\n"
+    assert machine.succeed("stat -c '%U' /var/empty") == "root\n"
+    assert machine.succeed("stat -c '%G' /var/empty") == "root\n"
+    assert "i" in machine.succeed("lsattr -d /var/empty")
+  '';
+}
diff --git a/nixos/tests/adguardhome.nix b/nixos/tests/adguardhome.nix
index 9f8ddc33f57e9..80613ce825340 100644
--- a/nixos/tests/adguardhome.nix
+++ b/nixos/tests/adguardhome.nix
@@ -7,7 +7,6 @@
     emptyConf = { lib, ... }: {
       services.adguardhome = {
         enable = true;
-        settings = {};
       };
     };
 
@@ -127,6 +126,7 @@
 
     with subtest("Testing successful DHCP start"):
         dhcpConf.wait_for_unit("adguardhome.service")
+        client.systemctl("start network-online.target")
         client.wait_for_unit("network-online.target")
         # Test IP assignment via DHCP
         dhcpConf.wait_until_succeeds("ping -c 5 10.0.10.100")
diff --git a/nixos/tests/akkoma.nix b/nixos/tests/akkoma.nix
index 7115c0beed34d..287e2d485999e 100644
--- a/nixos/tests/akkoma.nix
+++ b/nixos/tests/akkoma.nix
@@ -33,7 +33,10 @@ let
 
     echo '${userPassword}' | ${pkgs.toot}/bin/toot login_cli -i "akkoma.nixos.test" -e "jamy@nixos.test"
     echo "y" | ${pkgs.toot}/bin/toot post "hello world Jamy here"
-    echo "y" | ${pkgs.toot}/bin/toot timeline | grep -F -q "hello world Jamy here"
+
+    # Retrieving timeline with toot currently broken due to incompatible timestamp format
+    # cf. <https://akkoma.dev/AkkomaGang/akkoma/issues/637> and <https://github.com/ihabunek/toot/issues/399>
+    #echo "y" | ${pkgs.toot}/bin/toot timeline | grep -F -q "hello world Jamy here"
 
     # Test file upload
     echo "y" | ${pkgs.toot}/bin/toot upload <(dd if=/dev/zero bs=1024 count=1024 status=none) \
diff --git a/nixos/tests/all-terminfo.nix b/nixos/tests/all-terminfo.nix
index dd47c66ee1c1e..2f5e56f09f26b 100644
--- a/nixos/tests/all-terminfo.nix
+++ b/nixos/tests/all-terminfo.nix
@@ -10,7 +10,11 @@ import ./make-test-python.nix ({ pkgs, ... }: rec {
         let
           o = builtins.tryEval drv;
         in
-        o.success && lib.isDerivation o.value && o.value ? outputs && builtins.elem "terminfo" o.value.outputs;
+        o.success &&
+        lib.isDerivation o.value &&
+        o.value ? outputs &&
+        builtins.elem "terminfo" o.value.outputs &&
+        !o.value.meta.broken;
       terminfos = lib.filterAttrs infoFilter pkgs;
       excludedTerminfos = lib.filterAttrs (_: drv: !(builtins.elem drv.terminfo config.environment.systemPackages)) terminfos;
       includedOuts = lib.filterAttrs (_: drv: builtins.elem drv.out config.environment.systemPackages) terminfos;
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 681f2430c12d4..1453a3875f6e7 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -90,6 +90,14 @@ in {
     lib-extend = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./nixos-test-driver/lib-extend.nix {};
     node-name = runTest ./nixos-test-driver/node-name.nix;
     busybox = runTest ./nixos-test-driver/busybox.nix;
+    driver-timeout = pkgs.runCommand "ensure-timeout-induced-failure" {
+      failed = pkgs.testers.testBuildFailure ((runTest ./nixos-test-driver/timeout.nix).config.rawTestDerivation);
+    } ''
+      grep -F "timeout reached; test terminating" $failed/testBuildFailure.log
+      # The program will always be terminated by SIGTERM (143) if it waits for the deadline thread.
+      [[ 143 = $(cat $failed/testBuildFailure.exit) ]]
+      touch $out
+    '';
   };
 
   # NixOS vm tests and non-vm unit tests
@@ -109,18 +117,26 @@ in {
   allTerminfo = handleTest ./all-terminfo.nix {};
   alps = handleTest ./alps.nix {};
   amazon-init-shell = handleTest ./amazon-init-shell.nix {};
+  amazon-ssm-agent = handleTest ./amazon-ssm-agent.nix {};
+  amd-sev = runTest ./amd-sev.nix;
+  anbox = runTest ./anbox.nix;
+  angie-api = handleTest ./angie-api.nix {};
+  anki-sync-server = handleTest ./anki-sync-server.nix {};
   anuko-time-tracker = handleTest ./anuko-time-tracker.nix {};
   apcupsd = handleTest ./apcupsd.nix {};
   apfs = runTest ./apfs.nix;
   appliance-repart-image = runTest ./appliance-repart-image.nix;
   apparmor = handleTest ./apparmor.nix {};
+  archi = handleTest ./archi.nix {};
   atd = handleTest ./atd.nix {};
   atop = handleTest ./atop.nix {};
   atuin = handleTest ./atuin.nix {};
+  audiobookshelf = handleTest ./audiobookshelf.nix {};
   auth-mysql = handleTest ./auth-mysql.nix {};
   authelia = handleTest ./authelia.nix {};
   avahi = handleTest ./avahi.nix {};
   avahi-with-resolved = handleTest ./avahi.nix { networkd = true; };
+  ayatana-indicators = handleTest ./ayatana-indicators.nix {};
   babeld = handleTest ./babeld.nix {};
   bazarr = handleTest ./bazarr.nix {};
   bcachefs = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bcachefs.nix {};
@@ -150,15 +166,18 @@ in {
   budgie = handleTest ./budgie.nix {};
   buildbot = handleTest ./buildbot.nix {};
   buildkite-agents = handleTest ./buildkite-agents.nix {};
+  c2fmzq = handleTest ./c2fmzq.nix {};
   caddy = handleTest ./caddy.nix {};
   cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {};
   cage = handleTest ./cage.nix {};
   cagebreak = handleTest ./cagebreak.nix {};
   calibre-web = handleTest ./calibre-web.nix {};
   calibre-server = handleTest ./calibre-server.nix {};
+  castopod = handleTest ./castopod.nix {};
   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; };
+  centrifugo = runTest ./centrifugo.nix;
   ceph-multi-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-multi-node.nix {};
   ceph-single-node = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node.nix {};
   ceph-single-node-bluestore = handleTestOn [ "aarch64-linux" "x86_64-linux" ] ./ceph-single-node-bluestore.nix {};
@@ -170,6 +189,7 @@ in {
   chrony = handleTestOn ["aarch64-linux" "x86_64-linux"] ./chrony.nix {};
   chrony-ptp = handleTestOn ["aarch64-linux" "x86_64-linux"] ./chrony-ptp.nix {};
   cinnamon = handleTest ./cinnamon.nix {};
+  cinnamon-wayland = handleTest ./cinnamon-wayland.nix {};
   cjdns = handleTest ./cjdns.nix {};
   clickhouse = handleTest ./clickhouse.nix {};
   cloud-init = handleTest ./cloud-init.nix {};
@@ -210,7 +230,9 @@ in {
   custom-ca = handleTest ./custom-ca.nix {};
   croc = handleTest ./croc.nix {};
   darling = handleTest ./darling.nix {};
+  dae = handleTest ./dae.nix {};
   dconf = handleTest ./dconf.nix {};
+  deconz = handleTest ./deconz.nix {};
   deepin = handleTest ./deepin.nix {};
   deluge = handleTest ./deluge.nix {};
   dendrite = handleTest ./matrix/dendrite.nix {};
@@ -235,13 +257,16 @@ in {
   dolibarr = handleTest ./dolibarr.nix {};
   domination = handleTest ./domination.nix {};
   dovecot = handleTest ./dovecot.nix {};
+  drawterm = discoverTests (import ./drawterm.nix);
   drbd = handleTest ./drbd.nix {};
+  dublin-traceroute = handleTest ./dublin-traceroute.nix {};
   earlyoom = handleTestOn ["x86_64-linux"] ./earlyoom.nix {};
   early-mount-options = handleTest ./early-mount-options.nix {};
   ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
   ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
   ecryptfs = handleTest ./ecryptfs.nix {};
   fscrypt = handleTest ./fscrypt.nix {};
+  fastnetmon-advanced = runTest ./fastnetmon-advanced.nix;
   ejabberd = handleTest ./xmpp/ejabberd.nix {};
   elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
   emacs-daemon = handleTest ./emacs-daemon.nix {};
@@ -258,6 +283,11 @@ in {
   esphome = handleTest ./esphome.nix {};
   etc = pkgs.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; };
   activation = pkgs.callPackage ../modules/system/activation/test.nix { };
+  activation-var = runTest ./activation/var.nix;
+  activation-nix-channel = runTest ./activation/nix-channel.nix;
+  activation-etc-overlay-mutable = runTest ./activation/etc-overlay-mutable.nix;
+  activation-etc-overlay-immutable = runTest ./activation/etc-overlay-immutable.nix;
+  activation-perlless = runTest ./activation/perlless.nix;
   etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
   etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
   etebase-server = handleTest ./etebase-server.nix {};
@@ -266,29 +296,34 @@ in {
   fail2ban = handleTest ./fail2ban.nix { };
   fakeroute = handleTest ./fakeroute.nix {};
   fancontrol = handleTest ./fancontrol.nix {};
+  fanout = handleTest ./fanout.nix {};
   fcitx5 = handleTest ./fcitx5 {};
   fenics = handleTest ./fenics.nix {};
   ferm = handleTest ./ferm.nix {};
+  ferretdb = handleTest ./ferretdb.nix {};
   firefox = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox; };
   firefox-beta = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-beta; };
   firefox-devedition = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-devedition; };
   firefox-esr    = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job
-  firefox-esr-102 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-102; };
   firefox-esr-115 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-115; };
   firejail = handleTest ./firejail.nix {};
   firewall = handleTest ./firewall.nix { nftables = false; };
   firewall-nftables = handleTest ./firewall.nix { nftables = true; };
   fish = handleTest ./fish.nix {};
   flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
+  floorp = handleTest ./firefox.nix { firefoxPackage = pkgs.floorp; };
   fluentd = handleTest ./fluentd.nix {};
   fluidd = handleTest ./fluidd.nix {};
   fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
-  forgejo = handleTest ./gitea.nix { giteaPackage = pkgs.forgejo; };
+  forgejo = handleTest ./forgejo.nix { };
   freenet = handleTest ./freenet.nix {};
   freeswitch = handleTest ./freeswitch.nix {};
+  freetube = discoverTests (import ./freetube.nix);
   freshrss-sqlite = handleTest ./freshrss-sqlite.nix {};
   freshrss-pgsql = handleTest ./freshrss-pgsql.nix {};
+  freshrss-http-auth = handleTest ./freshrss-http-auth.nix {};
   frigate = handleTest ./frigate.nix {};
+  frp = handleTest ./frp.nix {};
   frr = handleTest ./frr.nix {};
   fsck = handleTest ./fsck.nix {};
   fsck-systemd-stage-1 = handleTest ./fsck.nix { systemdStage1 = true; };
@@ -297,6 +332,7 @@ in {
   mimir = handleTest ./mimir.nix {};
   garage = handleTest ./garage {};
   gemstash = handleTest ./gemstash.nix {};
+  geoserver = runTest ./geoserver.nix;
   gerrit = handleTest ./gerrit.nix {};
   geth = handleTest ./geth.nix {};
   ghostunnel = handleTest ./ghostunnel.nix {};
@@ -308,8 +344,10 @@ in {
   gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {};
   glusterfs = handleTest ./glusterfs.nix {};
   gnome = handleTest ./gnome.nix {};
+  gnome-extensions = handleTest ./gnome-extensions.nix {};
   gnome-flashback = handleTest ./gnome-flashback.nix {};
   gnome-xorg = handleTest ./gnome-xorg.nix {};
+  gns3-server = handleTest ./gns3-server.nix {};
   gnupg = handleTest ./gnupg.nix {};
   go-neb = handleTest ./go-neb.nix {};
   gobgpd = handleTest ./gobgpd.nix {};
@@ -318,6 +356,7 @@ in {
   gollum = handleTest ./gollum.nix {};
   gonic = handleTest ./gonic.nix {};
   google-oslogin = handleTest ./google-oslogin {};
+  goss = handleTest ./goss.nix {};
   gotify-server = handleTest ./gotify-server.nix {};
   gotosocial = runTest ./web-apps/gotosocial.nix;
   grafana = handleTest ./grafana {};
@@ -325,9 +364,10 @@ in {
   graphite = handleTest ./graphite.nix {};
   graylog = handleTest ./graylog.nix {};
   grocy = handleTest ./grocy.nix {};
+  grow-partition = runTest ./grow-partition.nix;
   grub = handleTest ./grub.nix {};
-  guacamole-client = handleTest ./guacamole-client.nix {};
   guacamole-server = handleTest ./guacamole-server.nix {};
+  guix = handleTest ./guix {};
   gvisor = handleTest ./gvisor.nix {};
   hadoop = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop; };
   hadoop_3_2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop_3_2; };
@@ -349,6 +389,7 @@ in {
   honk = runTest ./honk.nix;
   installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
   invidious = handleTest ./invidious.nix {};
+  livebook-service = handleTest ./livebook-service.nix {};
   oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {};
   odoo = handleTest ./odoo.nix {};
   odoo15 = handleTest ./odoo.nix { package = pkgs.odoo15; };
@@ -370,9 +411,10 @@ in {
   icingaweb2 = handleTest ./icingaweb2.nix {};
   iftop = handleTest ./iftop.nix {};
   incron = handleTest ./incron.nix {};
+  incus = pkgs.recurseIntoAttrs (handleTest ./incus { inherit handleTestOn; });
   influxdb = handleTest ./influxdb.nix {};
   influxdb2 = handleTest ./influxdb2.nix {};
-  initrd-network-openvpn = handleTest ./initrd-network-openvpn {};
+  initrd-network-openvpn = handleTestOn [ "x86_64-linux" "i686-linux" ] ./initrd-network-openvpn {};
   initrd-network-ssh = handleTest ./initrd-network-ssh {};
   initrd-luks-empty-passphrase = handleTest ./initrd-luks-empty-passphrase.nix {};
   initrdNetwork = handleTest ./initrd-network.nix {};
@@ -395,7 +437,7 @@ in {
   jibri = handleTest ./jibri.nix {};
   jirafeau = handleTest ./jirafeau.nix {};
   jitsi-meet = handleTest ./jitsi-meet.nix {};
-  jool = handleTest ./jool.nix {};
+  jool = import ./jool.nix { inherit pkgs runTest; };
   k3s = handleTest ./k3s {};
   kafka = handleTest ./kafka.nix {};
   kanidm = handleTest ./kanidm.nix {};
@@ -420,14 +462,16 @@ in {
   ksm = handleTest ./ksm.nix {};
   kthxbye = handleTest ./kthxbye.nix {};
   kubernetes = handleTestOn ["x86_64-linux"] ./kubernetes {};
-  kubo = runTest ./kubo.nix;
+  kubo = import ./kubo { inherit recurseIntoAttrs runTest; };
   ladybird = handleTest ./ladybird.nix {};
   languagetool = handleTest ./languagetool.nix {};
+  lanraragi = handleTest ./lanraragi.nix {};
   latestKernel.login = handleTest ./login.nix { latestKernel = true; };
   leaps = handleTest ./leaps.nix {};
   lemmy = handleTest ./lemmy.nix {};
   libinput = handleTest ./libinput.nix {};
   libreddit = handleTest ./libreddit.nix {};
+  librenms = handleTest ./librenms.nix {};
   libresprite = handleTest ./libresprite.nix {};
   libreswan = handleTest ./libreswan.nix {};
   librewolf = handleTest ./firefox.nix { firefoxPackage = pkgs.librewolf; };
@@ -446,7 +490,7 @@ in {
   loki = handleTest ./loki.nix {};
   luks = handleTest ./luks.nix {};
   lvm2 = handleTest ./lvm2 {};
-  lxd = pkgs.recurseIntoAttrs (handleTest ./lxd {});
+  lxd = pkgs.recurseIntoAttrs (handleTest ./lxd { inherit handleTestOn; });
   lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
   lorri = handleTest ./lorri/default.nix {};
@@ -466,6 +510,7 @@ in {
   matrix-appservice-irc = handleTest ./matrix/appservice-irc.nix {};
   matrix-conduit = handleTest ./matrix/conduit.nix {};
   matrix-synapse = handleTest ./matrix/synapse.nix {};
+  matrix-synapse-workers = handleTest ./matrix/synapse-workers.nix {};
   mattermost = handleTest ./mattermost.nix {};
   mediamtx = handleTest ./mediamtx.nix {};
   mediatomb = handleTest ./mediatomb.nix {};
@@ -483,6 +528,7 @@ in {
   miriway = handleTest ./miriway.nix {};
   misc = handleTest ./misc.nix {};
   mjolnir = handleTest ./matrix/mjolnir.nix {};
+  mobilizon = handleTest ./mobilizon.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
   molly-brown = handleTest ./molly-brown.nix {};
   monica = handleTest ./web-apps/monica.nix {};
@@ -503,6 +549,7 @@ in {
   munin = handleTest ./munin.nix {};
   mutableUsers = handleTest ./mutable-users.nix {};
   mxisd = handleTest ./mxisd.nix {};
+  mympd = handleTest ./mympd.nix {};
   mysql = handleTest ./mysql/mysql.nix {};
   mysql-autobackup = handleTest ./mysql/mysql-autobackup.nix {};
   mysql-backup = handleTest ./mysql/mysql-backup.nix {};
@@ -525,8 +572,8 @@ in {
   netdata = handleTest ./netdata.nix {};
   networking.networkd = handleTest ./networking.nix { networkd = true; };
   networking.scripted = handleTest ./networking.nix { networkd = false; };
-  netbox = handleTest ./web-apps/netbox.nix { inherit (pkgs) netbox; };
-  netbox_3_3 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_3; };
+  netbox_3_6 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_6; };
+  netbox_3_7 = handleTest ./web-apps/netbox.nix { netbox = pkgs.netbox_3_7; };
   netbox-upgrade = handleTest ./web-apps/netbox-upgrade.nix {};
   # TODO: put in networking.nix after the test becomes more complete
   networkingProxy = handleTest ./networking-proxy.nix {};
@@ -539,15 +586,19 @@ in {
   nginx = handleTest ./nginx.nix {};
   nginx-auth = handleTest ./nginx-auth.nix {};
   nginx-etag = handleTest ./nginx-etag.nix {};
+  nginx-etag-compression = handleTest ./nginx-etag-compression.nix {};
   nginx-globalredirect = handleTest ./nginx-globalredirect.nix {};
   nginx-http3 = handleTest ./nginx-http3.nix {};
   nginx-modsecurity = handleTest ./nginx-modsecurity.nix {};
+  nginx-moreheaders = handleTest ./nginx-moreheaders.nix {};
   nginx-njs = handleTest ./nginx-njs.nix {};
   nginx-proxyprotocol = handleTest ./nginx-proxyprotocol {};
   nginx-pubhtml = handleTest ./nginx-pubhtml.nix {};
-  nginx-sandbox = handleTestOn ["x86_64-linux"] ./nginx-sandbox.nix {};
+  nginx-redirectcode = handleTest ./nginx-redirectcode.nix {};
   nginx-sso = handleTest ./nginx-sso.nix {};
   nginx-status-page = handleTest ./nginx-status-page.nix {};
+  nginx-tmpdir = handleTest ./nginx-tmpdir.nix {};
+  nginx-unix-socket = handleTest ./nginx-unix-socket.nix {};
   nginx-variants = handleTest ./nginx-variants.nix {};
   nifi = handleTestOn ["x86_64-linux"] ./web-apps/nifi.nix {};
   nitter = handleTest ./nitter.nix {};
@@ -556,17 +607,24 @@ in {
   nix-serve-ssh = handleTest ./nix-serve-ssh.nix {};
   nixops = handleTest ./nixops/default.nix {};
   nixos-generate-config = handleTest ./nixos-generate-config.nix {};
-  nixos-rebuild-specialisations = handleTest ./nixos-rebuild-specialisations.nix {};
+  nixos-rebuild-install-bootloader = handleTestOn ["x86_64-linux"] ./nixos-rebuild-install-bootloader.nix {};
+  nixos-rebuild-specialisations = handleTestOn ["x86_64-linux"] ./nixos-rebuild-specialisations.nix {};
+  nixos-rebuild-target-host = handleTest ./nixos-rebuild-target-host.nix {};
   nixpkgs = pkgs.callPackage ../modules/misc/nixpkgs/test.nix { inherit evalMinimalConfig; };
+  nixseparatedebuginfod = handleTest ./nixseparatedebuginfod.nix {};
   node-red = handleTest ./node-red.nix {};
   nomad = handleTest ./nomad.nix {};
   non-default-filesystems = handleTest ./non-default-filesystems.nix {};
+  non-switchable-system = runTest ./non-switchable-system.nix;
   noto-fonts = handleTest ./noto-fonts.nix {};
   noto-fonts-cjk-qt-default-weight = handleTest ./noto-fonts-cjk-qt-default-weight.nix {};
   novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
+  npmrc = handleTest ./npmrc.nix {};
   nscd = handleTest ./nscd.nix {};
   nsd = handleTest ./nsd.nix {};
   ntfy-sh = handleTest ./ntfy-sh.nix {};
+  ntfy-sh-migration = handleTest ./ntfy-sh-migration.nix {};
+  ntpd-rs = handleTest ./ntpd-rs.nix {};
   nzbget = handleTest ./nzbget.nix {};
   nzbhydra2 = handleTest ./nzbhydra2.nix {};
   oh-my-zsh = handleTest ./oh-my-zsh.nix {};
@@ -584,6 +642,7 @@ in {
   openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
   opentabletdriver = handleTest ./opentabletdriver.nix {};
   opentelemetry-collector = handleTest ./opentelemetry-collector.nix {};
+  ocsinventory-agent = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./ocsinventory-agent.nix {};
   owncast = handleTest ./owncast.nix {};
   outline = handleTest ./outline.nix {};
   image-contents = handleTest ./image-contents.nix {};
@@ -624,6 +683,7 @@ in {
   phylactery = handleTest ./web-apps/phylactery.nix {};
   pict-rs = handleTest ./pict-rs.nix {};
   pinnwand = handleTest ./pinnwand.nix {};
+  plantuml-server = handleTest ./plantuml-server.nix {};
   plasma-bigscreen = handleTest ./plasma-bigscreen.nix {};
   plasma5 = handleTest ./plasma5.nix {};
   plasma5-systemd-start = handleTest ./plasma5-systemd-start.nix {};
@@ -653,7 +713,6 @@ in {
   predictable-interface-names = handleTest ./predictable-interface-names.nix {};
   printing-socket = handleTest ./printing.nix { socket = true; };
   printing-service = handleTest ./printing.nix { socket = false; };
-  privacyidea = handleTest ./privacyidea.nix {};
   privoxy = handleTest ./privoxy.nix {};
   prometheus = handleTest ./prometheus.nix {};
   prometheus-exporters = handleTest ./prometheus-exporters.nix {};
@@ -669,9 +728,13 @@ in {
   qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
   qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
   qemu-vm-volatile-root = runTest ./qemu-vm-volatile-root.nix;
-  quorum = handleTest ./quorum.nix {};
-  quake3 = handleTest ./quake3.nix {};
+  qemu-vm-external-disk-image = runTest ./qemu-vm-external-disk-image.nix;
+  qgis = handleTest ./qgis.nix { qgisPackage = pkgs.qgis; };
+  qgis-ltr = handleTest ./qgis.nix { qgisPackage = pkgs.qgis-ltr; };
   qownnotes = handleTest ./qownnotes.nix {};
+  quake3 = handleTest ./quake3.nix {};
+  quicktun = handleTest ./quicktun.nix {};
+  quorum = handleTest ./quorum.nix {};
   rabbitmq = handleTest ./rabbitmq.nix {};
   radarr = handleTest ./radarr.nix {};
   radicale = handleTest ./radicale.nix {};
@@ -683,10 +746,13 @@ in {
   restartByActivationScript = handleTest ./restart-by-activation-script.nix {};
   restic = handleTest ./restic.nix {};
   retroarch = handleTest ./retroarch.nix {};
+  rkvm = handleTest ./rkvm {};
   robustirc-bridge = handleTest ./robustirc-bridge.nix {};
   roundcube = handleTest ./roundcube.nix {};
+  rosenpass = handleTest ./rosenpass.nix {};
   rshim = handleTest ./rshim.nix {};
   rspamd = handleTest ./rspamd.nix {};
+  rspamd-trainer = handleTest ./rspamd-trainer.nix {};
   rss2email = handleTest ./rss2email.nix {};
   rstudio-server = handleTest ./rstudio-server.nix {};
   rsyncd = handleTest ./rsyncd.nix {};
@@ -701,10 +767,11 @@ in {
   sddm = handleTest ./sddm.nix {};
   seafile = handleTest ./seafile.nix {};
   searx = handleTest ./searx.nix {};
+  seatd = handleTest ./seatd.nix {};
   service-runner = handleTest ./service-runner.nix {};
   sftpgo = runTest ./sftpgo.nix;
   sfxr-qt = handleTest ./sfxr-qt.nix {};
-  sgtpuzzles = handleTest ./sgtpuzzles.nix {};
+  sgt-puzzles = handleTest ./sgt-puzzles.nix {};
   shadow = handleTest ./shadow.nix {};
   shadowsocks = handleTest ./shadowsocks {};
   shattered-pixel-dungeon = handleTest ./shattered-pixel-dungeon.nix {};
@@ -712,22 +779,28 @@ in {
   signal-desktop = handleTest ./signal-desktop.nix {};
   simple = handleTest ./simple.nix {};
   sing-box = handleTest ./sing-box.nix {};
+  slimserver = handleTest ./slimserver.nix {};
   slurm = handleTest ./slurm.nix {};
+  snmpd = handleTest ./snmpd.nix {};
   smokeping = handleTest ./smokeping.nix {};
   snapcast = handleTest ./snapcast.nix {};
   snapper = handleTest ./snapper.nix {};
   snipe-it = runTest ./web-apps/snipe-it.nix;
   soapui = handleTest ./soapui.nix {};
+  soft-serve = handleTest ./soft-serve.nix {};
   sogo = handleTest ./sogo.nix {};
   solanum = handleTest ./solanum.nix {};
   sonarr = handleTest ./sonarr.nix {};
+  sonic-server = handleTest ./sonic-server.nix {};
   sourcehut = handleTest ./sourcehut.nix {};
   spacecookie = handleTest ./spacecookie.nix {};
   spark = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./spark {};
   sqlite3-to-mysql = handleTest ./sqlite3-to-mysql.nix {};
   sslh = handleTest ./sslh.nix {};
-  sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {};
-  sssd-ldap = handleTestOn ["x86_64-linux"] ./sssd-ldap.nix {};
+  ssh-agent-auth = handleTest ./ssh-agent-auth.nix {};
+  ssh-audit = handleTest ./ssh-audit.nix {};
+  sssd = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./sssd.nix {};
+  sssd-ldap = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./sssd-ldap.nix {};
   stalwart-mail = handleTest ./stalwart-mail.nix {};
   stargazer = runTest ./web-servers/stargazer.nix;
   starship = handleTest ./starship.nix {};
@@ -735,8 +808,11 @@ in {
   step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
   stratis = handleTest ./stratis {};
   strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
+  stub-ld = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./stub-ld.nix {};
   stunnel = handleTest ./stunnel.nix {};
   sudo = handleTest ./sudo.nix {};
+  sudo-rs = handleTest ./sudo-rs.nix {};
+  suwayomi-server = handleTest ./suwayomi-server.nix {};
   swap-file-btrfs = handleTest ./swap-file-btrfs.nix {};
   swap-partition = handleTest ./swap-partition.nix {};
   swap-random-encryption = handleTest ./swap-random-encryption.nix {};
@@ -746,7 +822,9 @@ in {
   syncthing = handleTest ./syncthing.nix {};
   syncthing-no-settings = handleTest ./syncthing-no-settings.nix {};
   syncthing-init = handleTest ./syncthing-init.nix {};
+  syncthing-many-devices = handleTest ./syncthing-many-devices.nix {};
   syncthing-relay = handleTest ./syncthing-relay.nix {};
+  sysinit-reactivation = runTest ./sysinit-reactivation.nix;
   systemd = handleTest ./systemd.nix {};
   systemd-analyze = handleTest ./systemd-analyze.nix {};
   systemd-binfmt = handleTestOn ["x86_64-linux"] ./systemd-binfmt.nix {};
@@ -757,12 +835,14 @@ in {
   systemd-cryptenroll = handleTest ./systemd-cryptenroll.nix {};
   systemd-credentials-tpm2 = handleTest ./systemd-credentials-tpm2.nix {};
   systemd-escaping = handleTest ./systemd-escaping.nix {};
+  systemd-initrd-bridge = handleTest ./systemd-initrd-bridge.nix {};
   systemd-initrd-btrfs-raid = handleTest ./systemd-initrd-btrfs-raid.nix {};
   systemd-initrd-luks-fido2 = handleTest ./systemd-initrd-luks-fido2.nix {};
   systemd-initrd-luks-keyfile = handleTest ./systemd-initrd-luks-keyfile.nix {};
   systemd-initrd-luks-empty-passphrase = handleTest ./initrd-luks-empty-passphrase.nix { systemdStage1 = true; };
   systemd-initrd-luks-password = handleTest ./systemd-initrd-luks-password.nix {};
   systemd-initrd-luks-tpm2 = handleTest ./systemd-initrd-luks-tpm2.nix {};
+  systemd-initrd-luks-unl0kr = handleTest ./systemd-initrd-luks-unl0kr.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 {};
@@ -770,8 +850,11 @@ in {
   systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {};
   systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {};
   systemd-initrd-networkd-ssh = handleTest ./systemd-initrd-networkd-ssh.nix {};
-  systemd-initrd-networkd-openvpn = handleTest ./initrd-network-openvpn { systemdStage1 = true; };
+  systemd-initrd-networkd-openvpn = handleTestOn [ "x86_64-linux" "i686-linux" ] ./initrd-network-openvpn { systemdStage1 = true; };
+  systemd-initrd-vlan = handleTest ./systemd-initrd-vlan.nix {};
   systemd-journal = handleTest ./systemd-journal.nix {};
+  systemd-journal-gateway = handleTest ./systemd-journal-gateway.nix {};
+  systemd-journal-upload = handleTest ./systemd-journal-upload.nix {};
   systemd-machinectl = handleTest ./systemd-machinectl.nix {};
   systemd-networkd = handleTest ./systemd-networkd.nix {};
   systemd-networkd-dhcpserver = handleTest ./systemd-networkd-dhcpserver.nix {};
@@ -786,12 +869,17 @@ in {
   systemd-repart = handleTest ./systemd-repart.nix {};
   systemd-shutdown = handleTest ./systemd-shutdown.nix {};
   systemd-sysupdate = runTest ./systemd-sysupdate.nix;
+  systemd-sysusers-mutable = runTest ./systemd-sysusers-mutable.nix;
+  systemd-sysusers-immutable = runTest ./systemd-sysusers-immutable.nix;
   systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
+  systemd-timesyncd-nscd-dnssec = handleTest ./systemd-timesyncd-nscd-dnssec.nix {};
   systemd-user-tmpfiles-rules = handleTest ./systemd-user-tmpfiles-rules.nix {};
   systemd-misc = handleTest ./systemd-misc.nix {};
   systemd-userdbd = handleTest ./systemd-userdbd.nix {};
   systemd-homed = handleTest ./systemd-homed.nix {};
+  systemtap = handleTest ./systemtap.nix {};
   tandoor-recipes = handleTest ./tandoor-recipes.nix {};
+  tang = handleTest ./tang.nix {};
   taskserver = handleTest ./taskserver.nix {};
   tayga = handleTest ./tayga.nix {};
   teeworlds = handleTest ./teeworlds.nix {};
@@ -806,19 +894,23 @@ in {
   timezone = handleTest ./timezone.nix {};
   tinc = handleTest ./tinc {};
   tinydns = handleTest ./tinydns.nix {};
+  tinyproxy = handleTest ./tinyproxy.nix {};
   tinywl = handleTest ./tinywl.nix {};
   tmate-ssh-server = handleTest ./tmate-ssh-server.nix { };
   tomcat = handleTest ./tomcat.nix {};
   tor = handleTest ./tor.nix {};
   traefik = handleTestOn ["aarch64-linux" "x86_64-linux"] ./traefik.nix {};
   trafficserver = handleTest ./trafficserver.nix {};
-  transmission = handleTest ./transmission.nix {};
+  transmission = handleTest ./transmission.nix { transmission = pkgs.transmission; };
+  transmission_4 = handleTest ./transmission.nix { transmission = pkgs.transmission_4; };
   # 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 {};
+  tsja = handleTest ./tsja.nix {};
   tsm-client-gui = handleTest ./tsm-client-gui.nix {};
+  ttyd = handleTest ./web-servers/ttyd.nix {};
   txredisapi = handleTest ./txredisapi.nix {};
   tuptime = handleTest ./tuptime.nix {};
   turbovnc-headless-server = handleTest ./turbovnc-headless-server.nix {};
@@ -827,11 +919,12 @@ in {
   typesense = handleTest ./typesense.nix {};
   ucarp = handleTest ./ucarp.nix {};
   udisks2 = handleTest ./udisks2.nix {};
-  ulogd = handleTest ./ulogd.nix {};
+  ulogd = handleTest ./ulogd/ulogd.nix {};
   unbound = handleTest ./unbound.nix {};
   unifi = handleTest ./unifi.nix {};
   unit-php = handleTest ./web-servers/unit-php.nix {};
-  upnp = handleTest ./upnp.nix {};
+  upnp.iptables = handleTest ./upnp.nix { useNftables = false; };
+  upnp.nftables = handleTest ./upnp.nix { useNftables = true; };
   uptermd = handleTest ./uptermd.nix {};
   uptime-kuma = handleTest ./uptime-kuma.nix {};
   usbguard = handleTest ./usbguard.nix {};
@@ -841,8 +934,7 @@ in {
   uwsgi = handleTest ./uwsgi.nix {};
   v2ray = handleTest ./v2ray.nix {};
   varnish60 = handleTest ./varnish.nix { package = pkgs.varnish60; };
-  varnish72 = handleTest ./varnish.nix { package = pkgs.varnish72; };
-  varnish73 = handleTest ./varnish.nix { package = pkgs.varnish73; };
+  varnish74 = handleTest ./varnish.nix { package = pkgs.varnish74; };
   vault = handleTest ./vault.nix {};
   vault-agent = handleTest ./vault-agent.nix {};
   vault-dev = handleTest ./vault-dev.nix {};
@@ -858,6 +950,7 @@ in {
   vsftpd = handleTest ./vsftpd.nix {};
   warzone2100 = handleTest ./warzone2100.nix {};
   wasabibackend = handleTest ./wasabibackend.nix {};
+  watchdogd = handleTest ./watchdogd.nix {};
   webhook = runTest ./webhook.nix;
   wiki-js = handleTest ./wiki-js.nix {};
   wine = handleTest ./wine.nix {};
@@ -875,6 +968,8 @@ in {
   xmonad-xdg-autostart = handleTest ./xmonad-xdg-autostart.nix {};
   xpadneo = handleTest ./xpadneo.nix {};
   xrdp = handleTest ./xrdp.nix {};
+  xrdp-with-audio-pulseaudio = handleTest ./xrdp-with-audio-pulseaudio.nix {};
+  xscreensaver = handleTest ./xscreensaver.nix {};
   xss-lock = handleTest ./xss-lock.nix {};
   xterm = handleTest ./xterm.nix {};
   xxh = handleTest ./xxh.nix {};
@@ -889,4 +984,5 @@ in {
   zram-generator = handleTest ./zram-generator.nix {};
   zrepl = handleTest ./zrepl.nix {};
   zsh-history = handleTest ./zsh-history.nix {};
+  zwave-js = handleTest ./zwave-js.nix {};
 }
diff --git a/nixos/tests/amazon-ssm-agent.nix b/nixos/tests/amazon-ssm-agent.nix
new file mode 100644
index 0000000000000..957e9e0e02c5d
--- /dev/null
+++ b/nixos/tests/amazon-ssm-agent.nix
@@ -0,0 +1,17 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "amazon-ssm-agent";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.amazon-ssm-agent.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_file("/etc/amazon/ssm/seelog.xml")
+    machine.wait_for_file("/etc/amazon/ssm/amazon-ssm-agent.json")
+
+    machine.wait_for_unit("amazon-ssm-agent.service")
+  '';
+})
diff --git a/nixos/tests/amd-sev.nix b/nixos/tests/amd-sev.nix
new file mode 100644
index 0000000000000..bf9a50c10d0d2
--- /dev/null
+++ b/nixos/tests/amd-sev.nix
@@ -0,0 +1,56 @@
+{ lib, ... }: {
+  name = "amd-sev";
+  meta = {
+    maintainers = with lib.maintainers; [ trundle veehaitch ];
+  };
+
+  nodes.machine = { lib, ... }: {
+    hardware.cpu.amd.sev.enable = true;
+    hardware.cpu.amd.sevGuest.enable = true;
+
+    specialisation.sevCustomUserGroup.configuration = {
+      users.groups.sevtest = { };
+
+      hardware.cpu.amd.sev = {
+        enable = true;
+        group = "root";
+        mode = "0600";
+      };
+      hardware.cpu.amd.sevGuest = {
+        enable = true;
+        group = "sevtest";
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      specialisations = "${nodes.machine.system.build.toplevel}/specialisation";
+    in
+    ''
+      machine.wait_for_unit("multi-user.target")
+
+      with subtest("Check default settings"):
+        out = machine.succeed("cat /etc/udev/rules.d/99-local.rules")
+        assert 'KERNEL=="sev", OWNER="root", GROUP="sev", MODE="0660"' in out
+        assert 'KERNEL=="sev-guest", OWNER="root", GROUP="sev-guest", MODE="0660"' in out
+
+        out = machine.succeed("cat /etc/group")
+        assert "sev:" in out
+        assert "sev-guest:" in out
+        assert "sevtest:" not in out
+
+      with subtest("Activate configuration with custom user/group"):
+        machine.succeed('${specialisations}/sevCustomUserGroup/bin/switch-to-configuration test')
+
+      with subtest("Check custom user and group"):
+        out = machine.succeed("cat /etc/udev/rules.d/99-local.rules")
+        assert 'KERNEL=="sev", OWNER="root", GROUP="root", MODE="0600"' in out
+        assert 'KERNEL=="sev-guest", OWNER="root", GROUP="sevtest", MODE="0660"' in out
+
+        out = machine.succeed("cat /etc/group")
+        assert "sev:" not in out
+        assert "sev-guest:" not in out
+        assert "sevtest:" in out
+    '';
+}
diff --git a/nixos/tests/anbox.nix b/nixos/tests/anbox.nix
new file mode 100644
index 0000000000000..a00116536db7e
--- /dev/null
+++ b/nixos/tests/anbox.nix
@@ -0,0 +1,36 @@
+{ lib, pkgs, ... }:
+
+{
+  name = "anbox";
+  meta.maintainers = with lib.maintainers; [ mvnetbiz ];
+
+  nodes.machine = { pkgs, config, ... }: {
+    imports = [
+      ./common/user-account.nix
+      ./common/x11.nix
+    ];
+
+    environment.systemPackages = with pkgs; [ android-tools ];
+
+    test-support.displayManager.auto.user = "alice";
+
+    virtualisation.anbox.enable = true;
+    boot.kernelPackages = pkgs.linuxKernel.packages.linux_5_15;
+    virtualisation.memorySize = 2500;
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.machine.users.users.alice;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
+  in ''
+    machine.wait_for_x()
+
+    machine.wait_until_succeeds(
+        "sudo -iu alice ${bus} anbox wait-ready"
+    )
+
+    machine.wait_until_succeeds("adb shell true")
+
+    print(machine.succeed("adb devices"))
+  '';
+}
diff --git a/nixos/tests/angie-api.nix b/nixos/tests/angie-api.nix
new file mode 100644
index 0000000000000..4c8d6b54247b1
--- /dev/null
+++ b/nixos/tests/angie-api.nix
@@ -0,0 +1,148 @@
+import ./make-test-python.nix ({lib, pkgs, ...}:
+let
+  hosts = ''
+    192.168.2.101 example.com
+    192.168.2.101 api.example.com
+    192.168.2.101 backend.example.com
+  '';
+
+in
+{
+  name = "angie-api";
+  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 = [ 80 ];
+      };
+
+      services.nginx = {
+        enable = true;
+        package = pkgs.angie;
+
+        upstreams = {
+          "backend-http" = {
+            servers = { "backend.example.com:8080" = { fail_timeout = "0"; }; };
+            extraConfig = ''
+              zone upstream 256k;
+            '';
+          };
+          "backend-socket" = {
+            servers = { "unix:/run/example.sock" = { fail_timeout = "0"; }; };
+            extraConfig = ''
+              zone upstream 256k;
+            '';
+          };
+        };
+
+        virtualHosts."api.example.com" = {
+          locations."/console/" = {
+            extraConfig = ''
+              api /status/;
+
+              allow 192.168.2.201;
+              deny all;
+            '';
+          };
+        };
+
+        virtualHosts."example.com" = {
+          locations."/test/" = {
+            root = lib.mkForce (pkgs.runCommandLocal "testdir" {} ''
+              mkdir -p "$out/test"
+              cat > "$out/test/index.html" <<EOF
+              <html><body>Hello World!</body></html>
+              EOF
+            '');
+            extraConfig = ''
+              status_zone test_zone;
+
+              allow 192.168.2.201;
+              deny all;
+            '';
+          };
+          locations."/test/locked/" = {
+            extraConfig = ''
+              status_zone test_zone;
+
+              deny all;
+            '';
+          };
+          locations."/test/error/" = {
+            extraConfig = ''
+              status_zone test_zone;
+
+              allow all;
+            '';
+          };
+          locations."/upstream-http/" = {
+            proxyPass = "http://backend-http";
+          };
+          locations."/upstream-socket/" = {
+            proxyPass = "http://backend-socket";
+          };
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      networking = {
+        interfaces.eth1 = {
+          ipv4.addresses = [
+            { address = "192.168.2.201"; prefixLength = 24; }
+          ];
+        };
+        extraHosts = hosts;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    server.wait_for_unit("nginx")
+    server.wait_for_open_port(80)
+
+    # Check Angie version
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.angie.version' | grep '${pkgs.angie.version}'")
+
+    # Check access
+    client.succeed("curl --verbose --head http://api.example.com/console/ | grep 'HTTP/1.1 200'")
+    server.succeed("curl --verbose --head http://api.example.com/console/ | grep 'HTTP/1.1 403 Forbidden'")
+
+    # Check responses and requests
+    client.succeed("curl --verbose http://example.com/test/")
+    client.succeed("curl --verbose http://example.com/test/locked/")
+    client.succeed("curl --verbose http://example.com/test/locked/")
+    client.succeed("curl --verbose http://example.com/test/error/")
+    client.succeed("curl --verbose http://example.com/test/error/")
+    client.succeed("curl --verbose http://example.com/test/error/")
+    server.succeed("curl --verbose http://example.com/test/")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.location_zones.test_zone.responses.\"200\"' | grep '1'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.location_zones.test_zone.responses.\"403\"' | grep '3'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.location_zones.test_zone.responses.\"404\"' | grep '3'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.location_zones.test_zone.requests.total' | grep '7'")
+
+    # Check upstreams
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-http\".peers.\"192.168.2.101:8080\".state' | grep 'up'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-http\".peers.\"192.168.2.101:8080\".health.fails' | grep '0'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-socket\".peers.\"unix:/run/example.sock\".state' | grep 'up'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-socket\".peers.\"unix:/run/example.sock\".health.fails' | grep '0'")
+    client.succeed("curl --verbose http://example.com/upstream-http/")
+    client.succeed("curl --verbose http://example.com/upstream-socket/")
+    client.succeed("curl --verbose http://example.com/upstream-socket/")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-http\".peers.\"192.168.2.101:8080\".health.fails' | grep '1'")
+    client.succeed("curl --verbose http://api.example.com/console/ | jq -e '.http.upstreams.\"backend-socket\".peers.\"unix:/run/example.sock\".health.fails' | grep '2'")
+
+    server.shutdown()
+    client.shutdown()
+  '';
+})
diff --git a/nixos/tests/anki-sync-server.nix b/nixos/tests/anki-sync-server.nix
new file mode 100644
index 0000000000000..7d08cc9cb878e
--- /dev/null
+++ b/nixos/tests/anki-sync-server.nix
@@ -0,0 +1,71 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+  let
+    ankiSyncTest = pkgs.writeScript "anki-sync-test.py" ''
+      #!${pkgs.python3}/bin/python
+
+      import sys
+
+      # get site paths from anki itself
+      from runpy import run_path
+      run_path("${pkgs.anki}/bin/.anki-wrapped")
+      import anki
+
+      col = anki.collection.Collection('test_collection')
+      endpoint = 'http://localhost:27701'
+
+      # Sanity check: verify bad login fails
+      try:
+         col.sync_login('baduser', 'badpass', endpoint)
+         print("bad user login worked?!")
+         sys.exit(1)
+      except anki.errors.SyncError:
+          pass
+
+      # test logging in to users
+      col.sync_login('user', 'password', endpoint)
+      col.sync_login('passfileuser', 'passfilepassword', endpoint)
+
+      # Test actual sync. login apparently doesn't remember the endpoint...
+      login = col.sync_login('user', 'password', endpoint)
+      login.endpoint = endpoint
+      sync = col.sync_collection(login, False)
+      assert sync.required == sync.NO_CHANGES
+      # TODO: create an archive with server content including a test card
+      # and check we got it?
+    '';
+    testPasswordFile = pkgs.writeText "anki-password" "passfilepassword";
+  in
+  {
+  name = "anki-sync-server";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ martinetd ];
+  };
+
+  nodes.machine = { pkgs, ...}: {
+    services.anki-sync-server = {
+      enable = true;
+      users = [
+        { username = "user";
+          password = "password";
+        }
+        { username = "passfileuser";
+          passwordFile = testPasswordFile;
+        }
+      ];
+    };
+  };
+
+
+  testScript =
+    ''
+      start_all()
+
+      with subtest("Server starts successfully"):
+          # service won't start without users
+          machine.wait_for_unit("anki-sync-server.service")
+          machine.wait_for_open_port(27701)
+
+      with subtest("Can sync"):
+          machine.succeed("${ankiSyncTest}")
+    '';
+})
diff --git a/nixos/tests/appliance-repart-image.nix b/nixos/tests/appliance-repart-image.nix
index 3f256db846214..b18968d3b9631 100644
--- a/nixos/tests/appliance-repart-image.nix
+++ b/nixos/tests/appliance-repart-image.nix
@@ -8,9 +8,8 @@
 let
   rootPartitionLabel = "root";
 
-  bootLoaderConfigPath = "/loader/entries/nixos.conf";
-  kernelPath = "/EFI/nixos/kernel.efi";
-  initrdPath = "/EFI/nixos/initrd.efi";
+  imageId = "nixos-appliance";
+  imageVersion = "1-rc1";
 in
 {
   name = "appliance-gpt-image";
@@ -29,6 +28,9 @@ in
     # TODO(raitobezarius): revisit this when #244907 lands
     boot.loader.grub.enable = false;
 
+    system.image.id = imageId;
+    system.image.version = imageVersion;
+
     virtualisation.fileSystems = lib.mkForce {
       "/" = {
         device = "/dev/disk/by-partlabel/${rootPartitionLabel}";
@@ -48,19 +50,8 @@ in
               "/EFI/BOOT/BOOT${lib.toUpper efiArch}.EFI".source =
                 "${pkgs.systemd}/lib/systemd/boot/efi/systemd-boot${efiArch}.efi";
 
-              # TODO: create an abstraction for Boot Loader Specification (BLS) entries.
-              "${bootLoaderConfigPath}".source = pkgs.writeText "nixos.conf" ''
-                title NixOS
-                linux ${kernelPath}
-                initrd ${initrdPath}
-                options init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
-              '';
-
-              "${kernelPath}".source =
-                "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
-
-              "${initrdPath}".source =
-                "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
+              "/EFI/Linux/${config.system.boot.loader.ukiFile}".source =
+                "${config.system.build.uki}/${config.system.boot.loader.ukiFile}";
             };
           repartConfig = {
             Type = "esp";
@@ -99,7 +90,7 @@ in
       "-f",
       "qcow2",
       "-b",
-      "${nodes.machine.system.build.image}/image.raw",
+      "${nodes.machine.system.build.image}/${nodes.machine.image.repart.imageFile}",
       "-F",
       "raw",
       tmp_disk_image.name,
@@ -108,9 +99,11 @@ in
     # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
     os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
 
+    os_release = machine.succeed("cat /etc/os-release")
+    assert 'IMAGE_ID="${imageId}"' in os_release
+    assert 'IMAGE_VERSION="${imageVersion}"' in os_release
+
     bootctl_status = machine.succeed("bootctl status")
-    assert "${bootLoaderConfigPath}" in bootctl_status
-    assert "${kernelPath}" in bootctl_status
-    assert "${initrdPath}" in bootctl_status
+    assert "Boot Loader Specification Type #2 (.efi)" in bootctl_status
   '';
 }
diff --git a/nixos/tests/archi.nix b/nixos/tests/archi.nix
new file mode 100644
index 0000000000000..59f2e940c0050
--- /dev/null
+++ b/nixos/tests/archi.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "archi";
+  meta.maintainers = with lib.maintainers; [ paumr ];
+
+  nodes.machine = { pkgs, ... }: {
+    imports = [
+      ./common/x11.nix
+    ];
+
+    environment.systemPackages = with pkgs; [ archi ];
+  };
+
+  enableOCR = true;
+
+  testScript = ''
+    machine.wait_for_x()
+
+    with subtest("createEmptyModel via CLI"):
+         machine.succeed("Archi -application com.archimatetool.commandline.app -consoleLog -nosplash --createEmptyModel --saveModel smoke.archimate")
+         machine.copy_from_vm("smoke.archimate", "")
+
+    with subtest("UI smoketest"):
+         machine.succeed("DISPLAY=:0 Archi --createEmptyModel >&2 &")
+         machine.wait_for_window("Archi")
+
+         # wait till main UI is open
+         machine.wait_for_text("Welcome to Archi")
+
+         machine.screenshot("welcome-screen")
+  '';
+})
diff --git a/nixos/tests/audiobookshelf.nix b/nixos/tests/audiobookshelf.nix
new file mode 100644
index 0000000000000..64bd415160ee0
--- /dev/null
+++ b/nixos/tests/audiobookshelf.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ lib, ... }:
+
+with lib;
+
+{
+  name = "audiobookshelf";
+  meta.maintainers = with maintainers; [ wietsedv ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      services.audiobookshelf = {
+        enable = true;
+        port = 1234;
+      };
+    };
+
+  testScript = ''
+    machine.wait_for_unit("audiobookshelf.service")
+    machine.wait_for_open_port(1234)
+    machine.succeed("curl --fail http://localhost:1234/")
+  '';
+})
diff --git a/nixos/tests/auth-mysql.nix b/nixos/tests/auth-mysql.nix
index 0ed4b050a69a4..77a69eb1cd581 100644
--- a/nixos/tests/auth-mysql.nix
+++ b/nixos/tests/auth-mysql.nix
@@ -84,7 +84,7 @@ in
           getpwuid = ''
             SELECT name, 'x', uid, gid, name, CONCAT('/home/', name), "/run/current-system/sw/bin/bash" \
             FROM users \
-            WHERE id=%1$u \
+            WHERE uid=%1$u \
             LIMIT 1
           '';
           getspnam = ''
@@ -140,6 +140,7 @@ in
 
     machine.wait_for_unit("multi-user.target")
     machine.wait_for_unit("mysql.service")
+    machine.wait_until_succeeds("cat /etc/security/pam_mysql.conf | grep users.db_passwd")
     machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
 
     with subtest("Local login"):
diff --git a/nixos/tests/avahi.nix b/nixos/tests/avahi.nix
index c53a95903291c..d8f4d13340fbc 100644
--- a/nixos/tests/avahi.nix
+++ b/nixos/tests/avahi.nix
@@ -16,7 +16,7 @@ import ./make-test-python.nix {
     cfg = { ... }: {
       services.avahi = {
         enable = true;
-        nssmdns = true;
+        nssmdns4 = true;
         publish.addresses = true;
         publish.domain = true;
         publish.enable = true;
diff --git a/nixos/tests/ayatana-indicators.nix b/nixos/tests/ayatana-indicators.nix
new file mode 100644
index 0000000000000..c9cbbda4c601d
--- /dev/null
+++ b/nixos/tests/ayatana-indicators.nix
@@ -0,0 +1,110 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
+  user = "alice";
+in {
+  name = "ayatana-indicators";
+
+  meta = {
+    maintainers = lib.teams.lomiri.members;
+  };
+
+  nodes.machine = { config, ... }: {
+    imports = [
+      ./common/auto.nix
+      ./common/user-account.nix
+    ];
+
+    test-support.displayManager.auto = {
+      enable = true;
+      inherit user;
+    };
+
+    services.xserver = {
+      enable = true;
+      desktopManager.mate.enable = true;
+      displayManager.defaultSession = lib.mkForce "mate";
+    };
+
+    services.ayatana-indicators = {
+      enable = true;
+      packages = with pkgs; [
+        ayatana-indicator-datetime
+        ayatana-indicator-messages
+      ] ++ (with pkgs.lomiri; [
+        lomiri-indicator-network
+        telephony-service
+      ]);
+    };
+
+    # Setup needed by some indicators
+
+    services.accounts-daemon.enable = true; # messages
+
+    # Lomiri-ish setup for Lomiri indicators
+    # TODO move into a Lomiri module, once the package set is far enough for the DE to start
+
+    networking.networkmanager.enable = true; # lomiri-network-indicator
+    # TODO potentially urfkill for lomiri-network-indicator?
+
+    services.dbus.packages = with pkgs.lomiri; [
+      libusermetrics
+    ];
+
+    environment.systemPackages = with pkgs.lomiri; [
+      lomiri-schemas
+    ];
+
+    services.telepathy.enable = true;
+
+    users.users.usermetrics = {
+      group = "usermetrics";
+      home = "/var/lib/usermetrics";
+      createHome = true;
+      isSystemUser = true;
+    };
+
+    users.groups.usermetrics = { };
+  };
+
+  # TODO session indicator starts up in a semi-broken state, but works fine after a restart. maybe being started before graphical session is truly up & ready?
+  testScript = { nodes, ... }: let
+    runCommandOverServiceList = list: command:
+      lib.strings.concatMapStringsSep "\n" command list;
+
+    runCommandOverAyatanaIndicators = runCommandOverServiceList
+      (builtins.filter
+        (service: !(lib.strings.hasPrefix "lomiri" service || lib.strings.hasPrefix "telephony-service" service))
+        nodes.machine.systemd.user.targets."ayatana-indicators".wants);
+
+    runCommandOverAllIndicators = runCommandOverServiceList
+      nodes.machine.systemd.user.targets."ayatana-indicators".wants;
+  in ''
+    start_all()
+    machine.wait_for_x()
+
+    # Desktop environment should reach graphical-session.target
+    machine.wait_for_unit("graphical-session.target", "${user}")
+
+    # MATE relies on XDG autostart to bring up the indicators.
+    # Not sure *when* XDG autostart fires them up, and awaiting pgrep success seems to misbehave?
+    machine.sleep(10)
+
+    # Now check if all indicators were brought up successfully, and kill them for later
+  '' + (runCommandOverAyatanaIndicators (service: let serviceExec = builtins.replaceStrings [ "." ] [ "-" ] service; in ''
+    machine.succeed("pgrep -u ${user} -f ${serviceExec}")
+    machine.succeed("pkill -f ${serviceExec}")
+  '')) + ''
+
+    # Ayatana target is the preferred way of starting up indicators on SystemD session, the graphical session is responsible for starting this if it supports them.
+    # Mate currently doesn't do this, so start it manually for checking (https://github.com/mate-desktop/mate-indicator-applet/issues/63)
+    machine.systemctl("start ayatana-indicators.target", "${user}")
+    machine.wait_for_unit("ayatana-indicators.target", "${user}")
+
+    # Let all indicator services do their startups, potential post-launch crash & restart cycles so we can properly check for failures
+    # Not sure if there's a better way of awaiting this without false-positive potential
+    machine.sleep(10)
+
+    # Now check if all indicator services were brought up successfully
+  '' + runCommandOverAllIndicators (service: ''
+    machine.wait_for_unit("${service}", "${user}")
+  '');
+})
diff --git a/nixos/tests/babeld.nix b/nixos/tests/babeld.nix
index d4df6f86d089d..e497aa5b64e15 100644
--- a/nixos/tests/babeld.nix
+++ b/nixos/tests/babeld.nix
@@ -120,10 +120,6 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     ''
       start_all()
 
-      client.wait_for_unit("network-online.target")
-      local_router.wait_for_unit("network-online.target")
-      remote_router.wait_for_unit("network-online.target")
-
       local_router.wait_for_unit("babeld.service")
       remote_router.wait_for_unit("babeld.service")
 
diff --git a/nixos/tests/bcachefs.nix b/nixos/tests/bcachefs.nix
index 0385e098997f4..ec3c2427f386d 100644
--- a/nixos/tests/bcachefs.nix
+++ b/nixos/tests/bcachefs.nix
@@ -20,9 +20,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         "parted --script /dev/vdb mklabel msdos",
         "parted --script /dev/vdb -- mkpart primary 1024M 50% mkpart primary 50% -1s",
         "udevadm settle",
-        "keyctl link @u @s",
         "echo password | bcachefs format --encrypted --metadata_replicas 2 --label vtest /dev/vdb1 /dev/vdb2",
-        "echo password | bcachefs unlock /dev/vdb1",
+        "echo password | bcachefs unlock -k session /dev/vdb1",
         "echo password | mount -t bcachefs /dev/vdb1:/dev/vdb2 /tmp/mnt",
         "udevadm settle",
         "bcachefs fs usage /tmp/mnt",
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index 11420cba9dcec..473b05d4c98e8 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -115,6 +115,7 @@ in
       start_all()
 
       # Wait for network and miniupnpd.
+      router.systemctl("start network-online.target")
       router.wait_for_unit("network-online.target")
       router.wait_for_unit("miniupnpd")
 
@@ -129,6 +130,7 @@ in
       tracker.succeed("chmod 644 /tmp/test.torrent")
 
       # Start the tracker.  !!! use a less crappy tracker
+      tracker.systemctl("start network-online.target")
       tracker.wait_for_unit("network-online.target")
       tracker.wait_for_unit("opentracker.service")
       tracker.wait_for_open_port(6969)
@@ -140,6 +142,7 @@ in
 
       # Now we should be able to download from the client behind the NAT.
       tracker.wait_for_unit("httpd")
+      client1.systemctl("start network-online.target")
       client1.wait_for_unit("network-online.target")
       client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
       client1.wait_for_file("${download-dir}/test.tar.bz2")
@@ -148,10 +151,11 @@ in
       )
 
       # Bring down the initial seeder.
-      # tracker.stop_job("transmission")
+      tracker.stop_job("transmission")
 
       # Now download from the second client.  This can only succeed if
       # the first client created a NAT hole in the router.
+      client2.systemctl("start network-online.target")
       client2.wait_for_unit("network-online.target")
       client2.succeed(
           "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
diff --git a/nixos/tests/bootspec.nix b/nixos/tests/bootspec.nix
index 9295500422a92..14928b2206251 100644
--- a/nixos/tests/bootspec.nix
+++ b/nixos/tests/bootspec.nix
@@ -112,10 +112,39 @@ in
 
       bootspec = json.loads(machine.succeed("jq -r '.\"org.nixos.bootspec.v1\"' /run/current-system/boot.json"))
 
-      assert all(key in bootspec for key in ('initrd', 'initrdSecrets')), "Bootspec should contain initrd or initrdSecrets field when initrd is enabled"
+      assert 'initrd' in bootspec, "Bootspec should contain initrd field when initrd is enabled"
+      assert 'initrdSecrets' not in bootspec, "Bootspec should not contain initrdSecrets when there's no initrdSecrets"
     '';
   };
 
+  # Check that initrd secrets create corresponding entries in bootspec.
+  initrd-secrets = makeTest {
+    name = "bootspec-with-initrd-secrets";
+    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;
+      boot.initrd.secrets."/some/example" = pkgs.writeText "example-secret" "test";
+    };
+
+    testScript = ''
+      import json
+
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("test -e /run/current-system/boot.json")
+
+      bootspec = json.loads(machine.succeed("jq -r '.\"org.nixos.bootspec.v1\"' /run/current-system/boot.json"))
+
+      assert 'initrdSecrets' in bootspec, "Bootspec should contain an 'initrdSecrets' field given there's an initrd secret"
+    '';
+  };
+
+
   # Check that specialisations create corresponding entries in bootspec.
   specialisation = makeTest {
     name = "bootspec-with-specialisation";
diff --git a/nixos/tests/btrbk-section-order.nix b/nixos/tests/btrbk-section-order.nix
index 20f1afcf80ec7..6082de947f66f 100644
--- a/nixos/tests/btrbk-section-order.nix
+++ b/nixos/tests/btrbk-section-order.nix
@@ -29,10 +29,12 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   };
 
   testScript = ''
+    import difflib
     machine.wait_for_unit("basic.target")
-    got = machine.succeed("cat /etc/btrbk/local.conf")
+    got = machine.succeed("cat /etc/btrbk/local.conf").strip()
     expect = """
     backend btrfs-progs-sudo
+    stream_compress no
     timestamp_format long
     target ssh://global-target/
      ssh_user root
@@ -46,6 +48,9 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
        ssh_user root
     """.strip()
     print(got)
+    if got != expect:
+      diff = difflib.unified_diff(expect.splitlines(keepends=True), got.splitlines(keepends=True), fromfile="expected", tofile="got")
+      print("".join(diff))
     assert got == expect
   '';
 })
diff --git a/nixos/tests/btrbk.nix b/nixos/tests/btrbk.nix
index 5261321dfa2c5..403c9595530d8 100644
--- a/nixos/tests/btrbk.nix
+++ b/nixos/tests/btrbk.nix
@@ -27,7 +27,6 @@ import ./make-test-python.nix ({ pkgs, ... }:
         # don't do it with real ssh keys.
         environment.etc."btrbk_key".text = privateKey;
         services.btrbk = {
-          extraPackages = [ pkgs.lz4 ];
           instances = {
             remote = {
               onCalendar = "minutely";
diff --git a/nixos/tests/buildbot.nix b/nixos/tests/buildbot.nix
index 467c8d8baff40..149d73bba09c5 100644
--- a/nixos/tests/buildbot.nix
+++ b/nixos/tests/buildbot.nix
@@ -1,11 +1,6 @@
 # Test ensures buildbot master comes up correctly and workers can connect
 
-{ system ? builtins.currentSystem,
-  config ? {},
-  pkgs ? import ../.. { inherit system config; }
-}:
-
-import ./make-test-python.nix {
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "buildbot";
 
   nodes = {
@@ -76,6 +71,7 @@ import ./make-test-python.nix {
     gitrepo.wait_for_unit("multi-user.target")
 
     with subtest("Repo is accessible via git daemon"):
+        bbmaster.systemctl("start network-online.target")
         bbmaster.wait_for_unit("network-online.target")
         bbmaster.succeed("rm -rfv /tmp/fakerepo")
         bbmaster.succeed("git clone git://gitrepo/fakerepo /tmp/fakerepo")
@@ -83,6 +79,7 @@ import ./make-test-python.nix {
     with subtest("Master service and worker successfully connect"):
         bbmaster.wait_for_unit("buildbot-master.service")
         bbmaster.wait_until_succeeds("curl --fail -s --head http://bbmaster:8010")
+        bbworker.systemctl("start network-online.target")
         bbworker.wait_for_unit("network-online.target")
         bbworker.succeed("nc -z bbmaster 8010")
         bbworker.succeed("nc -z bbmaster 9989")
@@ -109,5 +106,5 @@ import ./make-test-python.nix {
         bbworker.fail("nc -z bbmaster 8011")
   '';
 
-  meta.maintainers = with pkgs.lib.maintainers; [ ];
-} {}
+  meta.maintainers = pkgs.lib.teams.buildbot.members;
+})
diff --git a/nixos/tests/c2fmzq.nix b/nixos/tests/c2fmzq.nix
new file mode 100644
index 0000000000000..0dd89f6881dd9
--- /dev/null
+++ b/nixos/tests/c2fmzq.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "c2FmZQ";
+  meta.maintainers = with lib.maintainers; [ hmenke ];
+
+  nodes.machine = {
+    services.c2fmzq-server = {
+      enable = true;
+      port = 8080;
+      passphraseFile = builtins.toFile "pwfile" "hunter2"; # don't do this on real deployments
+      settings = {
+        verbose = 3; # debug
+        # make sure multiple freeform options evaluate
+        allow-new-accounts = true;
+        auto-approve-new-accounts = true;
+        licenses = false;
+      };
+    };
+    environment = {
+      sessionVariables = {
+        C2FMZQ_PASSPHRASE = "lol";
+        C2FMZQ_API_SERVER = "http://localhost:8080";
+      };
+      systemPackages = [
+        pkgs.c2fmzq
+        (pkgs.writeScriptBin "c2FmZQ-client-wrapper" ''
+          #!${pkgs.expect}/bin/expect -f
+          spawn c2FmZQ-client {*}$argv
+          expect {
+            "Enter password:" { send "$env(PASSWORD)\r" }
+            "Type YES to confirm:" { send "YES\r" }
+            timeout { exit 1 }
+            eof { exit 0 }
+          }
+          interact
+        '')
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    machine.start()
+    machine.wait_for_unit("c2fmzq-server.service")
+    machine.wait_for_open_port(8080)
+
+    with subtest("Create accounts for alice and bob"):
+        machine.succeed("PASSWORD=foobar c2FmZQ-client-wrapper -- -v 3 create-account alice@example.com")
+        machine.succeed("PASSWORD=fizzbuzz c2FmZQ-client-wrapper -- -v 3 create-account bob@example.com")
+
+    with subtest("Log in as alice"):
+        machine.succeed("PASSWORD=foobar c2FmZQ-client-wrapper -- -v 3 login alice@example.com")
+        msg = machine.succeed("c2FmZQ-client -v 3 status")
+        assert "Logged in as alice@example.com" in msg, f"ERROR: Not logged in as alice:\n{msg}"
+
+    with subtest("Create a new album, upload a file, and delete the uploaded file"):
+        machine.succeed("c2FmZQ-client -v 3 create-album 'Rarest Memes'")
+        machine.succeed("echo 'pls do not steal' > meme.txt")
+        machine.succeed("c2FmZQ-client -v 3 import meme.txt 'Rarest Memes'")
+        machine.succeed("c2FmZQ-client -v 3 sync")
+        machine.succeed("rm meme.txt")
+
+    with subtest("Share the album with bob"):
+        machine.succeed("c2FmZQ-client-wrapper -- -v 3 share 'Rarest Memes' bob@example.com")
+
+    with subtest("Log in as bob"):
+        machine.succeed("PASSWORD=fizzbuzz c2FmZQ-client-wrapper -- -v 3 login bob@example.com")
+        msg = machine.succeed("c2FmZQ-client -v 3 status")
+        assert "Logged in as bob@example.com" in msg, f"ERROR: Not logged in as bob:\n{msg}"
+
+    with subtest("Download the shared file"):
+        machine.succeed("c2FmZQ-client -v 3 download 'shared/Rarest Memes/meme.txt'")
+        machine.succeed("c2FmZQ-client -v 3 export 'shared/Rarest Memes/meme.txt' .")
+        msg = machine.succeed("cat meme.txt")
+        assert "pls do not steal\n" == msg, f"File content is not the same:\n{msg}"
+
+    with subtest("Test that PWA is served"):
+        msg = machine.succeed("curl -sSfL http://localhost:8080")
+        assert "c2FmZQ" in msg, f"Could not find 'c2FmZQ' in the output:\n{msg}"
+
+    with subtest("A setting with false value is properly passed"):
+        machine.succeed("systemctl show -p ExecStart --value c2fmzq-server.service | grep -F -- '--licenses=false'");
+  '';
+})
diff --git a/nixos/tests/caddy.nix b/nixos/tests/caddy.nix
index 5a0d3539394b6..41d8e57de4686 100644
--- a/nixos/tests/caddy.nix
+++ b/nixos/tests/caddy.nix
@@ -48,11 +48,19 @@ import ./make-test-python.nix ({ pkgs, ... }: {
           };
         };
       };
+      specialisation.explicit-config-file.configuration = {
+        services.caddy.configFile = pkgs.writeText "Caddyfile" ''
+        localhost:80
+
+        respond "hello world"
+        '';
+      };
     };
   };
 
   testScript = { nodes, ... }:
     let
+      explicitConfigFile = "${nodes.webserver.system.build.toplevel}/specialisation/explicit-config-file";
       justReloadSystem = "${nodes.webserver.system.build.toplevel}/specialisation/config-reload";
       multipleConfigs = "${nodes.webserver.system.build.toplevel}/specialisation/multiple-configs";
       rfc42Config = "${nodes.webserver.system.build.toplevel}/specialisation/rfc42";
@@ -84,5 +92,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
           )
           webserver.wait_for_open_port(80)
           webserver.succeed("curl http://localhost | grep hello")
+
+      with subtest("explicit configFile"):
+          webserver.succeed(
+              "${explicitConfigFile}/bin/switch-to-configuration test >&2"
+          )
+          webserver.wait_for_open_port(80)
+          webserver.succeed("curl http://localhost | grep hello")
     '';
 })
diff --git a/nixos/tests/castopod.nix b/nixos/tests/castopod.nix
new file mode 100644
index 0000000000000..4435ec617d4e6
--- /dev/null
+++ b/nixos/tests/castopod.nix
@@ -0,0 +1,87 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "castopod";
+  meta = with lib.maintainers; {
+    maintainers = [ alexoundos misuzu ];
+  };
+  nodes.castopod = { nodes, ... }: {
+    networking.firewall.allowedTCPPorts = [ 80 ];
+    networking.extraHosts = ''
+      127.0.0.1 castopod.example.com
+    '';
+    services.castopod = {
+      enable = true;
+      database.createLocally = true;
+      localDomain = "castopod.example.com";
+    };
+    environment.systemPackages =
+      let
+        username = "admin";
+        email = "admin@castood.example.com";
+        password = "v82HmEp5";
+        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
+          from selenium.webdriver.support import expected_conditions as EC
+
+          options = Options()
+          options.add_argument('--headless')
+          driver = Firefox(options=options)
+          try:
+              driver.implicitly_wait(20)
+              driver.get('http://castopod.example.com/cp-install')
+
+              wait = WebDriverWait(driver, 10)
+
+              wait.until(EC.title_contains("installer"))
+
+              driver.find_element(By.CSS_SELECTOR, '#username').send_keys(
+                  '${username}'
+              )
+              driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
+                  '${email}'
+              )
+              driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
+                  '${password}'
+              )
+              driver.find_element(By.XPATH, "//button[contains(., 'Finish install')]").click()
+
+              wait.until(EC.title_contains("Auth"))
+
+              driver.find_element(By.CSS_SELECTOR, '#email').send_keys(
+                  '${email}'
+              )
+              driver.find_element(By.CSS_SELECTOR, '#password').send_keys(
+                  '${password}'
+              )
+              driver.find_element(By.XPATH, "//button[contains(., 'Login')]").click()
+
+              wait.until(EC.title_contains("Admin dashboard"))
+          finally:
+              driver.close()
+              driver.quit()
+        '';
+      in
+      [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
+  };
+  testScript = ''
+    start_all()
+    castopod.wait_for_unit("castopod-setup.service")
+    castopod.wait_for_file("/run/phpfpm/castopod.sock")
+    castopod.wait_for_unit("nginx.service")
+    castopod.wait_for_open_port(80)
+    castopod.wait_until_succeeds("curl -sS -f http://castopod.example.com")
+    castopod.succeed("curl -s http://localhost/cp-install | grep 'Create your Super Admin account' > /dev/null")
+
+    with subtest("Create superadmin and log in"):
+        castopod.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner")
+  '';
+})
diff --git a/nixos/tests/centrifugo.nix b/nixos/tests/centrifugo.nix
new file mode 100644
index 0000000000000..45c2904f5585f
--- /dev/null
+++ b/nixos/tests/centrifugo.nix
@@ -0,0 +1,80 @@
+let
+  redisPort = 6379;
+  centrifugoPort = 8080;
+  nodes = [
+    "centrifugo1"
+    "centrifugo2"
+    "centrifugo3"
+  ];
+in
+{ lib, ... }: {
+  name = "centrifugo";
+  meta.maintainers = [ lib.maintainers.tie ];
+
+  nodes = lib.listToAttrs (lib.imap0
+    (index: name: {
+      inherit name;
+      value = { config, ... }: {
+        services.centrifugo = {
+          enable = true;
+          settings = {
+            inherit name;
+            port = centrifugoPort;
+            # See https://centrifugal.dev/docs/server/engines#redis-sharding
+            engine = "redis";
+            # Connect to local Redis shard via Unix socket.
+            redis_address =
+              let
+                otherNodes = lib.take index nodes ++ lib.drop (index + 1) nodes;
+              in
+              map (name: "${name}:${toString redisPort}") otherNodes ++ [
+                "unix://${config.services.redis.servers.centrifugo.unixSocket}"
+              ];
+            usage_stats_disable = true;
+            api_insecure = true;
+          };
+          extraGroups = [
+            config.services.redis.servers.centrifugo.user
+          ];
+        };
+        services.redis.servers.centrifugo = {
+          enable = true;
+          bind = null; # all interfaces
+          port = redisPort;
+          openFirewall = true;
+          settings.protected-mode = false;
+        };
+      };
+    })
+    nodes);
+
+  testScript = ''
+    import json
+
+    redisPort = ${toString redisPort}
+    centrifugoPort = ${toString centrifugoPort}
+
+    start_all()
+
+    for machine in machines:
+      machine.wait_for_unit("redis-centrifugo.service")
+      machine.wait_for_open_port(redisPort)
+
+    for machine in machines:
+      machine.wait_for_unit("centrifugo.service")
+      machine.wait_for_open_port(centrifugoPort)
+
+    # See https://centrifugal.dev/docs/server/server_api#info
+    def list_nodes(machine):
+      curl = "curl --fail-with-body --silent"
+      body = "{}"
+      resp = json.loads(machine.succeed(f"{curl} -d '{body}' http://localhost:{centrifugoPort}/api/info"))
+      return resp["result"]["nodes"]
+    machineNames = {m.name for m in machines}
+    for machine in machines:
+      nodes = list_nodes(machine)
+      assert len(nodes) == len(machines)
+      nodeNames = {n['name'] for n in nodes}
+      assert machineNames == nodeNames
+  '';
+}
diff --git a/nixos/tests/ceph-single-node.nix b/nixos/tests/ceph-single-node.nix
index 4a5636fac1563..a3a4072365af8 100644
--- a/nixos/tests/ceph-single-node.nix
+++ b/nixos/tests/ceph-single-node.nix
@@ -182,16 +182,19 @@ let
     monA.wait_until_succeeds("ceph -s | grep 'mgr: ${cfg.monA.name}(active,'")
     monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
 
+    # This test has been commented out due to the upstream issue with pyo3
+    # that has broken this dashboard
+    # Reference: https://www.spinics.net/lists/ceph-users/msg77812.html
     # Enable the dashboard and recheck health
-    monA.succeed(
-        "ceph mgr module enable dashboard",
-        "ceph config set mgr mgr/dashboard/ssl false",
-        # default is 8080 but it's better to be explicit
-        "ceph config set mgr mgr/dashboard/server_port 8080",
-    )
-    monA.wait_for_open_port(8080)
-    monA.wait_until_succeeds("curl -q --fail http://localhost:8080")
-    monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
+    # monA.succeed(
+    #     "ceph mgr module enable dashboard",
+    #     "ceph config set mgr mgr/dashboard/ssl false",
+    #     # default is 8080 but it's better to be explicit
+    #     "ceph config set mgr mgr/dashboard/server_port 8080",
+    # )
+    # monA.wait_for_open_port(8080)
+    # monA.wait_until_succeeds("curl -q --fail http://localhost:8080")
+    # monA.wait_until_succeeds("ceph -s | grep 'HEALTH_OK'")
   '';
 in {
   name = "basic-single-node-ceph-cluster";
diff --git a/nixos/tests/cinnamon-wayland.nix b/nixos/tests/cinnamon-wayland.nix
new file mode 100644
index 0000000000000..824a606004cc0
--- /dev/null
+++ b/nixos/tests/cinnamon-wayland.nix
@@ -0,0 +1,77 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "cinnamon-wayland";
+
+  meta.maintainers = lib.teams.cinnamon.members;
+
+  nodes.machine = { nodes, ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.xserver.enable = true;
+    services.xserver.desktopManager.cinnamon.enable = true;
+    services.xserver.displayManager = {
+      autoLogin.enable = true;
+      autoLogin.user = nodes.machine.users.users.alice.name;
+      defaultSession = "cinnamon-wayland";
+    };
+
+    # For the sessionPath subtest.
+    services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gnome.gpaste ];
+  };
+
+  enableOCR = true;
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
+      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 = name: su "gdbus call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval ${name}";
+    in
+    ''
+      machine.wait_for_unit("display-manager.service")
+
+      with subtest("Wait for wayland server"):
+          machine.wait_for_file("/run/user/${toString user.uid}/wayland-0")
+
+      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')
+          # https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
+          machine.wait_until_succeeds("${eval "Main.runState"} | grep -q 'true,..2'")
+
+      with subtest("Check if Cinnamon components actually start"):
+          for i in ["csd-media-keys", "xapp-sn-watcher", "nemo-desktop"]:
+            machine.wait_until_succeeds(f"pgrep -f {i}")
+          machine.wait_until_succeeds("journalctl -b --grep 'Loaded applet menu@cinnamon.org'")
+          machine.wait_until_succeeds("journalctl -b --grep 'calendar@cinnamon.org: Calendar events supported'")
+
+      with subtest("Check if sessionPath option actually works"):
+          machine.succeed("${eval "imports.gi.GIRepository.Repository.get_search_path\\(\\)"} | grep gpaste")
+
+      with subtest("Open Cinnamon Settings"):
+          machine.succeed("${su "cinnamon-settings themes >&2 &"}")
+          machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'cinnamon-settings'")
+          machine.wait_for_text('(Style|Appearance|Color)')
+          machine.sleep(2)
+          machine.screenshot("cinnamon_settings")
+
+      with subtest("Check if screensaver works"):
+          # This is not supported at the moment.
+          # https://trello.com/b/HHs01Pab/cinnamon-wayland
+          machine.execute("${su "cinnamon-screensaver-command -l >&2 &"}")
+          machine.wait_until_succeeds("journalctl -b --grep 'Cinnamon Screensaver is unavailable on Wayland'")
+
+      with subtest("Open GNOME Terminal"):
+          machine.succeed("${su "dbus-launch gnome-terminal"}")
+          machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'gnome-terminal'")
+          machine.sleep(2)
+
+      with subtest("Check if Cinnamon has ever coredumped"):
+          machine.fail("coredumpctl --json=short | grep -E 'cinnamon|nemo'")
+    '';
+})
diff --git a/nixos/tests/cinnamon.nix b/nixos/tests/cinnamon.nix
index 2a1389231904c..eab907d0b712c 100644
--- a/nixos/tests/cinnamon.nix
+++ b/nixos/tests/cinnamon.nix
@@ -7,6 +7,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     imports = [ ./common/user-account.nix ];
     services.xserver.enable = true;
     services.xserver.desktopManager.cinnamon.enable = true;
+
+    # For the sessionPath subtest.
+    services.xserver.desktopManager.cinnamon.sessionPath = [ pkgs.gnome.gpaste ];
   };
 
   enableOCR = true;
@@ -14,27 +17,13 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   testScript = { nodes, ... }:
     let
       user = nodes.machine.users.users.alice;
-      uid = toString user.uid;
-      bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
-      display = "DISPLAY=:0.0";
-      env = "${bus} ${display}";
-      gdbus = "${env} gdbus";
+      env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0";
       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";
+      eval = name: su "gdbus call --session -d org.Cinnamon -o /org/Cinnamon -m org.Cinnamon.Eval ${name}";
     in
     ''
       machine.wait_for_unit("display-manager.service")
@@ -54,13 +43,46 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
       with subtest("Wait for the Cinnamon shell"):
           # Correct output should be (true, '2')
-          machine.wait_until_succeeds("${getRunState} | grep -q 'true,..2'")
+          # https://github.com/linuxmint/cinnamon/blob/5.4.0/js/ui/main.js#L183-L187
+          machine.wait_until_succeeds("${eval "Main.runState"} | grep -q 'true,..2'")
+
+      with subtest("Check if Cinnamon components actually start"):
+          for i in ["csd-media-keys", "cinnamon-killer-daemon", "xapp-sn-watcher", "nemo-desktop"]:
+            machine.wait_until_succeeds(f"pgrep -f {i}")
+          machine.wait_until_succeeds("journalctl -b --grep 'Loaded applet menu@cinnamon.org'")
+          machine.wait_until_succeeds("journalctl -b --grep 'calendar@cinnamon.org: Calendar events supported'")
+
+      with subtest("Check if sessionPath option actually works"):
+          machine.succeed("${eval "imports.gi.GIRepository.Repository.get_search_path\\(\\)"} | grep gpaste")
+
+      with subtest("Open Cinnamon Settings"):
+          machine.succeed("${su "cinnamon-settings themes >&2 &"}")
+          machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'cinnamon-settings'")
+          machine.wait_for_text('(Style|Appearance|Color)')
+          machine.sleep(2)
+          machine.screenshot("cinnamon_settings")
+
+      with subtest("Lock the screen"):
+          machine.succeed("${su "cinnamon-screensaver-command -l >&2 &"}")
+          machine.wait_until_succeeds("${su "cinnamon-screensaver-command -q"} | grep 'The screensaver is active'")
+          machine.sleep(2)
+          machine.screenshot("cinnamon_screensaver")
+          machine.send_chars("${user.password}\n", delay=0.2)
+          machine.wait_until_succeeds("${su "cinnamon-screensaver-command -q"} | grep 'The screensaver is inactive'")
+          machine.sleep(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")
+          machine.succeed("${su "gnome-terminal"}")
+          machine.wait_until_succeeds("${eval "global.display.focus_window.wm_class"} | grep -i 'gnome-terminal'")
+          machine.sleep(2)
+
+      with subtest("Open virtual keyboard"):
+          machine.succeed("${su "dbus-send --print-reply --dest=org.Cinnamon /org/Cinnamon org.Cinnamon.ToggleKeyboard"}")
+          machine.wait_for_text('(Ctrl|Alt)')
+          machine.sleep(2)
+          machine.screenshot("cinnamon_virtual_keyboard")
+
+      with subtest("Check if Cinnamon has ever coredumped"):
+          machine.fail("coredumpctl --json=short | grep -E 'cinnamon|nemo'")
     '';
 })
diff --git a/nixos/tests/cloud-init.nix b/nixos/tests/cloud-init.nix
index 786e01add7d4b..0b4c5a55c80a6 100644
--- a/nixos/tests/cloud-init.nix
+++ b/nixos/tests/cloud-init.nix
@@ -73,6 +73,7 @@ in makeTest {
   };
   testScript = ''
     # To wait until cloud-init terminates its run
+    unnamed.wait_for_unit("cloud-init-local.service")
     unnamed.wait_for_unit("cloud-final.service")
 
     unnamed.succeed("cat /tmp/cloudinit-write-file | grep -q 'cloudinit'")
diff --git a/nixos/tests/cockpit.nix b/nixos/tests/cockpit.nix
index 6f86d1e2c464c..e7165b9790141 100644
--- a/nixos/tests/cockpit.nix
+++ b/nixos/tests/cockpit.nix
@@ -50,7 +50,8 @@ import ./make-test-python.nix (
             options = Options()
             options.add_argument("--headless")
 
-            driver = webdriver.Firefox(options=options)
+            service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+            driver = webdriver.Firefox(options=options, service=service)
 
             driver.implicitly_wait(10)
 
diff --git a/nixos/tests/common/auto-format-root-device.nix b/nixos/tests/common/auto-format-root-device.nix
index 56eecef2f4110..fef8c70049913 100644
--- a/nixos/tests/common/auto-format-root-device.nix
+++ b/nixos/tests/common/auto-format-root-device.nix
@@ -5,19 +5,19 @@
 # `virtualisation.fileSystems."/".autoFormat = true;`
 # instead.
 
-{ config, pkgs, ... }:
+{ lib, config, pkgs, ... }:
 
 let
   rootDevice = config.virtualisation.rootDevice;
 in
 {
 
-  boot.initrd.extraUtilsCommands = ''
+  boot.initrd.extraUtilsCommands = lib.mkIf (!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 (!config.boot.initrd.systemd.enable) ''
     # If the disk image appears to be empty, run mke2fs to
     # initialise.
     FSTYPE=$(blkid -o value -s TYPE ${rootDevice} || true)
diff --git a/nixos/tests/containers-custom-pkgs.nix b/nixos/tests/containers-custom-pkgs.nix
index e8740ac631345..57184787c85f6 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 erikarvstedt ];
+    maintainers = with lib.maintainers; [ erikarvstedt ];
   };
 
   nodes.machine = { config, ... }: {
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 22b664a90e170..fff00e4f73a85 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -13,6 +13,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       nix.settings.sandbox = false;
       nix.settings.substituters = []; # don't try to access cache.nixos.org
 
+      virtualisation.memorySize = 2048;
       virtualisation.writableStore = true;
       # Make sure we always have all the required dependencies for creating a
       # container available within the VM, because we don't have network access.
@@ -21,9 +22,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           modules = lib.singleton {
             nixpkgs = { inherit (config.nixpkgs) localSystem; };
 
-            containers.foo.config = {
-              system.stateVersion = "18.03";
-            };
+            containers.foo.config = {};
           };
 
           # The system is inherited from the host above.
diff --git a/nixos/tests/containers-ip.nix b/nixos/tests/containers-ip.nix
index ecead5c22f756..ecff99a3f0c25 100644
--- a/nixos/tests/containers-ip.nix
+++ b/nixos/tests/containers-ip.nix
@@ -19,10 +19,7 @@ in import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
   nodes.machine =
     { pkgs, ... }: {
-      imports = [ ../modules/installer/cd-dvd/channel.nix ];
-      virtualisation = {
-        writableStore = true;
-      };
+      virtualisation.writableStore = true;
 
       containers.webserver4 = webserverFor "10.231.136.1" "10.231.136.2";
       containers.webserver6 = webserverFor "fc00::2" "fc00::1";
diff --git a/nixos/tests/convos.nix b/nixos/tests/convos.nix
index 8fe5892da9e52..06d8d30fcb148 100644
--- a/nixos/tests/convos.nix
+++ b/nixos/tests/convos.nix
@@ -22,7 +22,6 @@ in
   testScript = ''
     machine.wait_for_unit("convos")
     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 b6f5d7fc6f75b..dd2bec794a1a0 100644
--- a/nixos/tests/corerad.nix
+++ b/nixos/tests/corerad.nix
@@ -56,6 +56,8 @@ import ./make-test-python.nix (
 
       with subtest("Wait for CoreRAD and network ready"):
           # Ensure networking is online and CoreRAD is ready.
+          router.systemctl("start network-online.target")
+          client.systemctl("start network-online.target")
           router.wait_for_unit("network-online.target")
           client.wait_for_unit("network-online.target")
           router.wait_for_unit("corerad.service")
diff --git a/nixos/tests/curl-impersonate.nix b/nixos/tests/curl-impersonate.nix
index 7954e9e5584c4..33b10da1dfd0f 100644
--- a/nixos/tests/curl-impersonate.nix
+++ b/nixos/tests/curl-impersonate.nix
@@ -144,6 +144,8 @@ in {
     start_all()
 
     with subtest("Wait for network"):
+        web.systemctl("start network-online.target")
+        curl.systemctl("start network-online.target")
         web.wait_for_unit("network-online.target")
         curl.wait_for_unit("network-online.target")
 
diff --git a/nixos/tests/dae.nix b/nixos/tests/dae.nix
new file mode 100644
index 0000000000000..42a2eb5fe0be5
--- /dev/null
+++ b/nixos/tests/dae.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+
+  name = "dae";
+
+  meta = {
+    maintainers = with lib.maintainers; [ oluceps ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.nginx = {
+      enable = true;
+      statusPage = true;
+    };
+    services.dae = {
+      enable = true;
+      config = ''
+        global{}
+        routing{}
+      '';
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_unit("dae.service")
+
+    machine.wait_for_open_port(80)
+
+    machine.succeed("curl --fail --max-time 10 http://localhost")
+  '';
+
+})
diff --git a/nixos/tests/dconf.nix b/nixos/tests/dconf.nix
index 86f703e3b98e8..192c075540a40 100644
--- a/nixos/tests/dconf.nix
+++ b/nixos/tests/dconf.nix
@@ -14,8 +14,8 @@ import ./make-test-python.nix
         profiles.user.databases = [
           {
             settings = {
-              "test/not/locked" = mkInt32 1;
-              "test/is/locked" = "locked";
+              "test/not".locked = mkInt32 1;
+              "test/is".locked = "locked";
             };
             locks = [
               "/test/is/locked"
diff --git a/nixos/tests/deconz.nix b/nixos/tests/deconz.nix
new file mode 100644
index 0000000000000..cbe721ba49251
--- /dev/null
+++ b/nixos/tests/deconz.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  httpPort = 800;
+in
+{
+  name = "deconz";
+
+  meta.maintainers = with lib.maintainers; [
+    bjornfor
+  ];
+
+  nodes.machine = { config, pkgs, lib, ... }: {
+    nixpkgs.config.allowUnfree = true;
+    services.deconz = {
+      enable = true;
+      inherit httpPort;
+      extraArgs = [
+        "--dbg-err=2"
+        "--dbg-info=2"
+      ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("deconz.service")
+    machine.succeed("curl -sfL http://localhost:${toString httpPort}")
+  '';
+})
diff --git a/nixos/tests/deepin.nix b/nixos/tests/deepin.nix
index 7b2e2430f31c7..d3ce79a535c13 100644
--- a/nixos/tests/deepin.nix
+++ b/nixos/tests/deepin.nix
@@ -36,12 +36,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       with subtest("Check that logging in has given the user ownership of devices"):
           machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
 
-      with subtest("Check if DDE wm chooser actually start"):
-          machine.wait_until_succeeds("pgrep -f dde-wm-chooser")
-          machine.wait_for_window("dde-wm-chooser")
-          machine.execute("pkill dde-wm-chooser")
-
-
       with subtest("Check if Deepin session components actually start"):
           machine.wait_until_succeeds("pgrep -f dde-session-daemon")
           machine.wait_for_window("dde-session-daemon")
diff --git a/nixos/tests/dex-oidc.nix b/nixos/tests/dex-oidc.nix
index 37275a97ef0fb..e54ae18ca9373 100644
--- a/nixos/tests/dex-oidc.nix
+++ b/nixos/tests/dex-oidc.nix
@@ -49,7 +49,7 @@ import ./make-test-python.nix ({ lib, ... }: {
       ensureUsers = [
         {
           name = "dex";
-          ensurePermissions = { "DATABASE dex" = "ALL PRIVILEGES"; };
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/tests/dhparams.nix b/nixos/tests/dhparams.nix
index 021042fafdb10..8d7082c114001 100644
--- a/nixos/tests/dhparams.nix
+++ b/nixos/tests/dhparams.nix
@@ -18,6 +18,8 @@ import ./make-test-python.nix {
         systemd.services.foo = {
           description = "Check systemd Ordering";
           wantedBy = [ "multi-user.target" ];
+          before = [ "shutdown.target" ];
+          conflicts = [ "shutdown.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
diff --git a/nixos/tests/discourse.nix b/nixos/tests/discourse.nix
index c79ba41c2eb9c..3e69a314905cc 100644
--- a/nixos/tests/discourse.nix
+++ b/nixos/tests/discourse.nix
@@ -166,7 +166,7 @@ import ./make-test-python.nix (
         request = builtins.toJSON {
           title = "Private message";
           raw = "This is a test message.";
-          target_usernames = admin.username;
+          target_recipients = admin.username;
           archetype = "private_message";
         };
       in ''
diff --git a/nixos/tests/dnscrypt-wrapper/default.nix b/nixos/tests/dnscrypt-wrapper/default.nix
index 1c05376e097b3..1a794931dc500 100644
--- a/nixos/tests/dnscrypt-wrapper/default.nix
+++ b/nixos/tests/dnscrypt-wrapper/default.nix
@@ -1,5 +1,15 @@
+
 { lib, pkgs, ... }:
 
+let
+  snakeoil = import ../common/acme/server/snakeoil-certs.nix;
+
+  hosts = lib.mkForce
+   { "fd::a" = [ "server" snakeoil.domain ];
+     "fd::b" = [ "client" ];
+   };
+in
+
 {
   name = "dnscrypt-wrapper";
   meta = with pkgs.lib.maintainers; {
@@ -7,59 +17,122 @@
   };
 
   nodes = {
-    server = { lib, ... }:
-      { services.dnscrypt-wrapper = with builtins;
+    server = {
+      networking.hosts = hosts;
+      networking.interfaces.eth1.ipv6.addresses = lib.singleton
+        { address = "fd::a"; prefixLength = 64; };
+
+        services.dnscrypt-wrapper =
           { enable = true;
-            address = "192.168.1.1";
+            address = "[::]";
+            port = 5353;
             keys.expiration = 5; # days
             keys.checkInterval = 2;  # min
             # The keypair was generated by the command:
             # dnscrypt-wrapper --gen-provider-keypair \
             #  --provider-name=2.dnscrypt-cert.server \
-            #  --ext-address=192.168.1.1:5353
-            providerKey.public = toFile "public.key" (readFile ./public.key);
-            providerKey.secret = toFile "secret.key" (readFile ./secret.key);
+            providerKey.public = "${./public.key}";
+            providerKey.secret = "${./secret.key}";
+          };
+
+        # nameserver
+        services.bind.enable = true;
+        services.bind.zones = lib.singleton
+          { name = ".";
+            master = true;
+            file = pkgs.writeText "root.zone" ''
+              $TTL 3600
+              . IN SOA example.org. admin.example.org. ( 1 3h 1h 1w 1d )
+              . IN NS example.org.
+              example.org. IN AAAA 2001:db8::1
+            '';
+          };
+
+        # webserver
+        services.nginx.enable = true;
+        services.nginx.virtualHosts.${snakeoil.domain} =
+          { onlySSL = true;
+            listenAddresses = [ "localhost" ];
+            sslCertificate = snakeoil.${snakeoil.domain}.cert;
+            sslCertificateKey = snakeoil.${snakeoil.domain}.key;
+            locations."/ip".extraConfig = ''
+              default_type text/plain;
+              return 200 "Ciao $remote_addr!\n";
+            '';
           };
-        services.tinydns.enable = true;
-        services.tinydns.data = ''
-          ..:192.168.1.1:a
-          +it.works:1.2.3.4
-        '';
-        networking.firewall.allowedUDPPorts = [ 5353 ];
-        networking.firewall.allowedTCPPorts = [ 5353 ];
-        networking.interfaces.eth1.ipv4.addresses = lib.mkForce
-          [ { address = "192.168.1.1"; prefixLength = 24; } ];
+
+        # demultiplex HTTP and DNS from port 443
+        services.sslh =
+          { enable = true;
+            method = "ev";
+            settings.transparent = true;
+            settings.listen = lib.mkForce
+              [ { host = "server"; port = "443"; is_udp = false; }
+                { host = "server"; port = "443"; is_udp = true; }
+              ];
+            settings.protocols =
+              [ # Send TLS to webserver (TCP)
+                { name = "tls"; host= "localhost"; port= "443"; }
+                # Send DNSCrypt to dnscrypt-wrapper (TCP or UDP)
+                { name = "anyprot"; host = "localhost"; port = "5353"; }
+                { name = "anyprot"; host = "localhost"; port = "5353"; is_udp = true;}
+              ];
+          };
+
+        networking.firewall.allowedTCPPorts = [ 443 ];
+        networking.firewall.allowedUDPPorts = [ 443 ];
       };
 
-    client = { lib, ... }:
-      { services.dnscrypt-proxy2.enable = true;
-        services.dnscrypt-proxy2.upstreamDefaults = false;
-        services.dnscrypt-proxy2.settings = {
-          server_names = [ "server" ];
-          static.server.stamp = "sdns://AQAAAAAAAAAAEDE5Mi4xNjguMS4xOjUzNTMgFEHYOv0SCKSuqR5CDYa7-58cCBuXO2_5uTSVU9wNQF0WMi5kbnNjcnlwdC1jZXJ0LnNlcnZlcg";
+    client = {
+      networking.hosts = hosts;
+      networking.interfaces.eth1.ipv6.addresses = lib.singleton
+        { address = "fd::b"; prefixLength = 64; };
+
+      services.dnscrypt-proxy2.enable = true;
+      services.dnscrypt-proxy2.upstreamDefaults = false;
+      services.dnscrypt-proxy2.settings =
+        { server_names = [ "server" ];
+          listen_addresses = [ "[::1]:53" ];
+          cache = false;
+          # Computed using https://dnscrypt.info/stamps/
+          static.server.stamp =
+            "sdns://AQAAAAAAAAAADzE5Mi4xNjguMS4yOjQ0MyAUQdg6"
+            +"_RIIpK6pHkINhrv7nxwIG5c7b_m5NJVT3A1AXRYyLmRuc2NyeXB0LWNlcnQuc2VydmVy";
         };
-        networking.nameservers = [ "127.0.0.1" ];
-        networking.interfaces.eth1.ipv4.addresses = lib.mkForce
-          [ { address = "192.168.1.2"; prefixLength = 24; } ];
-      };
+      networking.nameservers = [ "::1" ];
+      security.pki.certificateFiles = [ snakeoil.ca.cert ];
+    };
 
   };
 
   testScript = ''
-    start_all()
-
     with subtest("The server can generate the ephemeral keypair"):
         server.wait_for_unit("dnscrypt-wrapper")
         server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.key")
         server.wait_for_file("/var/lib/dnscrypt-wrapper/2.dnscrypt-cert.server.crt")
         almost_expiration = server.succeed("date --date '4days 23 hours 56min'").strip()
 
-    with subtest("The client can connect to the server"):
-        server.wait_for_unit("tinydns")
-        client.wait_for_unit("dnscrypt-proxy2")
-        assert "1.2.3.4" in client.wait_until_succeeds(
-            "host it.works"
-        ), "The IP address of 'it.works' does not match 1.2.3.4"
+    with subtest("The DNSCrypt client can connect to the server"):
+        server.wait_for_unit("sslh")
+        client.wait_until_succeeds("journalctl -u dnscrypt-proxy2 --grep '\[server\] OK'")
+
+    with subtest("HTTP client can connect to the server"):
+        server.wait_for_unit("nginx")
+        client.succeed("curl -s --fail https://${snakeoil.domain}/ip | grep -q fd::b")
+
+    with subtest("DNS queries over UDP are working"):
+        server.wait_for_unit("bind")
+        client.wait_for_open_port(53)
+        assert "2001:db8::1" in client.wait_until_succeeds(
+            "host -U example.org"
+        ), "The IP address of 'example.org' does not match 2001:db8::1"
+
+    with subtest("DNS queries over TCP are working"):
+        server.wait_for_unit("bind")
+        client.wait_for_open_port(53)
+        assert "2001:db8::1" in client.wait_until_succeeds(
+            "host -T example.org"
+        ), "The IP address of 'example.org' does not match 2001:db8::1"
 
     with subtest("The server rotates the ephemeral keys"):
         # advance time by a little less than 5 days
@@ -68,7 +141,8 @@
         server.wait_for_file("/var/lib/dnscrypt-wrapper/oldkeys")
 
     with subtest("The client can still connect to the server"):
-        server.wait_for_unit("dnscrypt-wrapper")
-        client.succeed("host it.works")
+        client.systemctl("restart dnscrypt-proxy2")
+        client.wait_until_succeeds("host -T example.org")
+        client.wait_until_succeeds("host -U example.org")
   '';
 }
diff --git a/nixos/tests/docker-registry.nix b/nixos/tests/docker-registry.nix
index 316b7c9b97273..db20cb52c3e3a 100644
--- a/nixos/tests/docker-registry.nix
+++ b/nixos/tests/docker-registry.nix
@@ -3,7 +3,7 @@
 import ./make-test-python.nix ({ pkgs, ...} : {
   name = "docker-registry";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ globin ma27 ironpinguin ];
+    maintainers = [ globin ironpinguin ];
   };
 
   nodes = {
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index 44b583ebcea55..90af817e75ed3 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -11,7 +11,7 @@ let
       # Rootfs diffs for layers 1 and 2 are identical (and empty)
       layer1 = pkgs.dockerTools.buildImage {  name = "empty";  };
       layer2 = layer1.overrideAttrs (_: { fromImage = layer1; });
-      repeatedRootfsDiffs = pkgs.runCommandNoCC "image-with-links.tar" {
+      repeatedRootfsDiffs = pkgs.runCommand "image-with-links.tar" {
         nativeBuildInputs = [pkgs.jq];
       } ''
         mkdir contents
@@ -55,7 +55,7 @@ in {
   nodes = {
     docker = { ... }: {
       virtualisation = {
-        diskSize = 2048;
+        diskSize = 3072;
         docker.enable = true;
       };
     };
diff --git a/nixos/tests/documize.nix b/nixos/tests/documize.nix
index fda79b1a09318..3624c0c56769c 100644
--- a/nixos/tests/documize.nix
+++ b/nixos/tests/documize.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, lib, ...} : {
   name = "documize";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ma27 ];
+    maintainers = [ ];
   };
 
   nodes.machine = { pkgs, ... }: {
diff --git a/nixos/tests/dolibarr.nix b/nixos/tests/dolibarr.nix
index 4fdee9e9698f5..95557d317fab9 100644
--- a/nixos/tests/dolibarr.nix
+++ b/nixos/tests/dolibarr.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "dolibarr";
-  meta.maintainers = [ lib.maintainers.raitobezarius ];
+  meta.maintainers = [ ];
 
   nodes.machine =
     { ... }:
diff --git a/nixos/tests/drawterm.nix b/nixos/tests/drawterm.nix
new file mode 100644
index 0000000000000..1d444bb55433b
--- /dev/null
+++ b/nixos/tests/drawterm.nix
@@ -0,0 +1,58 @@
+{ system, pkgs }:
+let
+  tests = {
+    xorg = {
+      node = { pkgs, ... }: {
+        imports = [ ./common/user-account.nix ./common/x11.nix ];
+        services.xserver.enable = true;
+        services.xserver.displayManager.sessionCommands = ''
+          ${pkgs.drawterm}/bin/drawterm -g 1024x768 &
+        '';
+        test-support.displayManager.auto.user = "alice";
+      };
+      systems = [ "x86_64-linux" "aarch64-linux" ];
+    };
+    wayland = {
+      node = { pkgs, ... }: {
+        imports = [ ./common/wayland-cage.nix ];
+        services.cage.program = "${pkgs.drawterm-wayland}/bin/drawterm";
+      };
+      systems = [ "x86_64-linux" ];
+    };
+  };
+
+  mkTest = name: machine:
+    import ./make-test-python.nix ({ pkgs, ... }: {
+      inherit name;
+
+      nodes = { "${name}" = machine; };
+
+      meta = with pkgs.lib.maintainers; {
+        maintainers = [ moody ];
+      };
+
+      enableOCR = true;
+
+      testScript = ''
+        @polling_condition
+        def drawterm_running():
+            machine.succeed("pgrep drawterm")
+
+        start_all()
+
+        machine.wait_for_unit("graphical.target")
+        drawterm_running.wait() # type: ignore[union-attr]
+        machine.wait_for_text("cpu")
+        machine.send_chars("cpu\n")
+        machine.wait_for_text("auth")
+        machine.send_chars("cpu\n")
+        machine.wait_for_text("ending")
+        machine.screenshot("out.png")
+      '';
+
+    });
+  mkTestOn = systems: name: machine:
+    if pkgs.lib.elem system systems then mkTest name machine
+    else { ... }: { };
+in
+builtins.mapAttrs (k: v: mkTestOn v.systems k v.node { inherit system; }) tests
diff --git a/nixos/tests/dublin-traceroute.nix b/nixos/tests/dublin-traceroute.nix
new file mode 100644
index 0000000000000..b359b7fcdd6fb
--- /dev/null
+++ b/nixos/tests/dublin-traceroute.nix
@@ -0,0 +1,63 @@
+# This is a simple distributed test involving a topology with two
+# separate virtual networks - the "inside" and the "outside" - with a
+# client on the inside network, a server on the outside network, and a
+# router connected to both that performs Network Address Translation
+# for the client.
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+  let
+    routerBase =
+      lib.mkMerge [
+        { virtualisation.vlans = [ 2 1 ];
+          networking.nftables.enable = true;
+          networking.nat.internalIPs = [ "192.168.1.0/24" ];
+          networking.nat.externalInterface = "eth1";
+        }
+      ];
+  in
+  {
+    name = "dublin-traceroute";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ baloo ];
+    };
+
+    nodes.client = { nodes, ... }: {
+      imports = [ ./common/user-account.nix ];
+      virtualisation.vlans = [ 1 ];
+
+      networking.defaultGateway =
+        (builtins.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address;
+      networking.nftables.enable = true;
+
+      programs.dublin-traceroute.enable = true;
+    };
+
+    nodes.router = { ... }: {
+      virtualisation.vlans = [ 2 1 ];
+      networking.nftables.enable = true;
+      networking.nat.internalIPs = [ "192.168.1.0/24" ];
+      networking.nat.externalInterface = "eth1";
+      networking.nat.enable = true;
+    };
+
+    nodes.server = { ... }: {
+      virtualisation.vlans = [ 2 ];
+      networking.firewall.enable = false;
+      services.httpd.enable = true;
+      services.httpd.adminAddr = "foo@example.org";
+      services.vsftpd.enable = true;
+      services.vsftpd.anonymousUser = true;
+    };
+
+    testScript = ''
+      client.start()
+      router.start()
+      server.start()
+
+      server.wait_for_unit("network.target")
+      router.wait_for_unit("network.target")
+      client.wait_for_unit("network.target")
+
+      # Make sure we can trace from an unprivileged user
+      client.succeed("sudo -u alice dublin-traceroute server")
+    '';
+  })
diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix
index 0122bc440361d..b5a8cb532ae0a 100644
--- a/nixos/tests/elk.nix
+++ b/nixos/tests/elk.nix
@@ -1,6 +1,6 @@
 # To run the test on the unfree ELK use the following command:
 # cd path/to/nixpkgs
-# NIXPKGS_ALLOW_UNFREE=1 nix-build -A nixosTests.elk.unfree.ELK-6
+# NIXPKGS_ALLOW_UNFREE=1 nix-build -A nixosTests.elk.unfree.ELK-7
 
 { system ? builtins.currentSystem,
   config ? {},
@@ -119,13 +119,8 @@ let
                 package = elk.elasticsearch;
               };
 
-              kibana = {
-                enable = true;
-                package = elk.kibana;
-              };
-
               elasticsearch-curator = {
-                enable = true;
+                enable = elk ? elasticsearch-curator;
                 actionYAML = ''
                 ---
                 actions:
@@ -217,13 +212,6 @@ let
           one.wait_until_succeeds("cat /tmp/logstash.out | grep flowers")
           one.wait_until_succeeds("cat /tmp/logstash.out | grep -v dragons")
 
-      with subtest("Kibana is healthy"):
-          one.wait_for_unit("kibana.service")
-          one.wait_until_succeeds(
-              "curl --silent --show-error --fail-with-body 'http://localhost:5601/api/status'"
-              + " | jq -es 'if . == [] then null else .[] | .status.overall.state == \"green\" end'"
-          )
-
       with subtest("Metricbeat is running"):
           one.wait_for_unit("metricbeat.service")
 
@@ -258,7 +246,7 @@ let
           one.wait_until_succeeds(
               expect_hits("SuperdupercalifragilisticexpialidociousIndeed")
           )
-    '' + ''
+    '' + lib.optionalString (elk ? elasticsearch-curator) ''
       with subtest("Elasticsearch-curator works"):
           one.systemctl("stop logstash")
           one.systemctl("start elasticsearch-curator")
@@ -274,7 +262,6 @@ in {
   #   name = "elk-7";
   #   elasticsearch = pkgs.elasticsearch7-oss;
   #   logstash      = pkgs.logstash7-oss;
-  #   kibana        = pkgs.kibana7-oss;
   #   filebeat      = pkgs.filebeat7;
   #   metricbeat    = pkgs.metricbeat7;
   # };
@@ -282,7 +269,6 @@ in {
     ELK-7 = mkElkTest "elk-7" {
       elasticsearch = pkgs.elasticsearch7;
       logstash      = pkgs.logstash7;
-      kibana        = pkgs.kibana7;
       filebeat      = pkgs.filebeat7;
       metricbeat    = pkgs.metricbeat7;
     };
diff --git a/nixos/tests/eris-server.nix b/nixos/tests/eris-server.nix
index a50db3afebf5f..b9d2b57401e0a 100644
--- a/nixos/tests/eris-server.nix
+++ b/nixos/tests/eris-server.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   meta.maintainers = with lib.maintainers; [ ehmry ];
 
   nodes.server = {
-    environment.systemPackages = [ pkgs.eris-go pkgs.nim.pkgs.eris ];
+    environment.systemPackages = [ pkgs.eris-go pkgs.eriscmd ];
     services.eris-server = {
       enable = true;
       decode = true;
diff --git a/nixos/tests/fanout.nix b/nixos/tests/fanout.nix
new file mode 100644
index 0000000000000..c36d34dcce0be
--- /dev/null
+++ b/nixos/tests/fanout.nix
@@ -0,0 +1,30 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+}:
+import ./make-test-python.nix ({lib, pkgs, ...}: {
+  name = "fanout";
+  meta.maintainers = [ lib.maintainers.therishidesai ];
+
+  nodes = let
+    cfg = { ... }: {
+      services.fanout = {
+        enable = true;
+        fanoutDevices = 2;
+        bufferSize = 8192;
+      };
+    };
+  in {
+    machine = cfg;
+  };
+
+  testScript = ''
+    start_all()
+
+    # mDNS.
+    machine.wait_for_unit("multi-user.target")
+
+    machine.succeed("test -c /dev/fanout0")
+    machine.succeed("test -c /dev/fanout1")
+  '';
+})
diff --git a/nixos/tests/fastnetmon-advanced.nix b/nixos/tests/fastnetmon-advanced.nix
new file mode 100644
index 0000000000000..b2d2713a92110
--- /dev/null
+++ b/nixos/tests/fastnetmon-advanced.nix
@@ -0,0 +1,65 @@
+{ pkgs, lib, ... }:
+
+{
+  name = "fastnetmon-advanced";
+  meta.maintainers = lib.teams.wdz.members;
+
+  nodes = {
+    bird = { ... }: {
+      networking.firewall.allowedTCPPorts = [ 179 ];
+      services.bird2 = {
+        enable = true;
+        config = ''
+          router id 192.168.1.1;
+
+          protocol bgp fnm {
+            local 192.168.1.1 as 64513;
+            neighbor 192.168.1.2 as 64514;
+            multihop;
+            ipv4 {
+              import all;
+              export none;
+            };
+          }
+        '';
+      };
+    };
+    fnm = { ... }: {
+      networking.firewall.allowedTCPPorts = [ 179 ];
+      services.fastnetmon-advanced = {
+        enable = true;
+        settings = {
+          networks_list = [ "172.23.42.0/24" ];
+          gobgp = true;
+          gobgp_flow_spec_announces = true;
+        };
+        bgpPeers = {
+          bird = {
+            local_asn = 64514;
+            remote_asn = 64513;
+            local_address = "192.168.1.2";
+            remote_address = "192.168.1.1";
+
+            description = "Bird";
+            ipv4_unicast = true;
+            multihop = true;
+            active = true;
+          };
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+    fnm.wait_for_unit("fastnetmon.service")
+    bird.wait_for_unit("bird2.service")
+
+    fnm.wait_until_succeeds('journalctl -eu fastnetmon.service | grep "BGP daemon restarted correctly"')
+    fnm.wait_until_succeeds("journalctl -eu gobgp.service | grep BGP_FSM_OPENCONFIRM")
+    bird.wait_until_succeeds("birdc show protocol fnm | grep Estab")
+    fnm.wait_until_succeeds('journalctl -eu fastnetmon.service | grep "API server listening"')
+    fnm.succeed("fcli set blackhole 172.23.42.123")
+    bird.succeed("birdc show route | grep 172.23.42.123")
+  '';
+}
diff --git a/nixos/tests/ferm.nix b/nixos/tests/ferm.nix
index be43877445ebf..87c67ac623479 100644
--- a/nixos/tests/ferm.nix
+++ b/nixos/tests/ferm.nix
@@ -55,6 +55,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     ''
       start_all()
 
+      client.systemctl("start network-online.target")
+      server.systemctl("start network-online.target")
       client.wait_for_unit("network-online.target")
       server.wait_for_unit("network-online.target")
       server.wait_for_unit("ferm.service")
diff --git a/nixos/tests/ferretdb.nix b/nixos/tests/ferretdb.nix
new file mode 100644
index 0000000000000..7251198af77dc
--- /dev/null
+++ b/nixos/tests/ferretdb.nix
@@ -0,0 +1,64 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; }
+, ...
+}:
+let
+  lib = pkgs.lib;
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("ferretdb.service")
+    machine.wait_for_open_port(27017)
+    machine.succeed("mongosh --eval 'use myNewDatabase;' --eval 'db.myCollection.insertOne( { x: 1 } );'")
+  '';
+in
+with import ../lib/testing-python.nix { inherit system; };
+{
+
+  postgresql = makeTest
+    {
+      inherit testScript;
+      name = "ferretdb-postgresql";
+      meta.maintainers = with lib.maintainers; [ julienmalka ];
+
+      nodes.machine =
+        { pkgs, ... }:
+        {
+          services.ferretdb = {
+            enable = true;
+            settings.FERRETDB_HANDLER = "pg";
+            settings.FERRETDB_POSTGRESQL_URL = "postgres://ferretdb@localhost/ferretdb?host=/run/postgresql";
+          };
+
+          systemd.services.ferretdb.serviceConfig = {
+            Requires = "postgresql.service";
+            After = "postgresql.service";
+          };
+
+          services.postgresql = {
+            enable = true;
+            ensureDatabases = [ "ferretdb" ];
+            ensureUsers = [{
+              name = "ferretdb";
+              ensureDBOwnership = true;
+            }];
+          };
+
+          environment.systemPackages = with pkgs; [ mongosh ];
+        };
+    };
+
+  sqlite = makeTest
+    {
+      inherit testScript;
+      name = "ferretdb-sqlite";
+      meta.maintainers = with lib.maintainers; [ julienmalka ];
+
+      nodes.machine =
+        { pkgs, ... }:
+        {
+          services.ferretdb.enable = true;
+
+          environment.systemPackages = with pkgs; [ mongosh ];
+        };
+    };
+}
diff --git a/nixos/tests/firefox.nix b/nixos/tests/firefox.nix
index 3f9cea6662fbe..fbea95dc75235 100644
--- a/nixos/tests/firefox.nix
+++ b/nixos/tests/firefox.nix
@@ -1,14 +1,7 @@
 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;
+  name = firefoxPackage.pname;
+
   meta = with pkgs.lib.maintainers; {
     maintainers = [ eelco shlevy ];
   };
@@ -17,10 +10,13 @@ in
     { pkgs, ... }:
 
     { imports = [ ./common/x11.nix ];
-      environment.systemPackages = [
-        firefoxPackage'
-        pkgs.xdotool
-      ];
+      environment.systemPackages = [ pkgs.xdotool ];
+
+      programs.firefox = {
+        enable = true;
+        preferences."media.autoplay.default" = 0;
+        package = firefoxPackage;
+      };
 
       # Create a virtual sound device, with mixing
       # and all, for recording audio.
@@ -58,7 +54,9 @@ in
 
     };
 
-  testScript = ''
+  testScript = let
+    exe = firefoxPackage.unwrapped.binaryName;
+  in ''
       from contextlib import contextmanager
 
 
@@ -97,7 +95,7 @@ in
 
       with subtest("Wait until Firefox has finished loading the Valgrind docs page"):
           machine.execute(
-              "xterm -e '${firefoxPackage'.unwrapped.binaryName} file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' >&2 &"
+              "xterm -e '${exe} file://${pkgs.valgrind.doc}/share/doc/valgrind/html/index.html' >&2 &"
           )
           machine.wait_for_window("Valgrind")
           machine.sleep(40)
@@ -105,7 +103,7 @@ in
       with subtest("Check whether Firefox can play sound"):
           with record_audio(machine):
               machine.succeed(
-                  "${firefoxPackage'.unwrapped.binaryName} file://${pkgs.sound-theme-freedesktop}/share/sounds/freedesktop/stereo/phone-incoming-call.oga >&2 &"
+                  "${exe} 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/fontconfig-default-fonts.nix b/nixos/tests/fontconfig-default-fonts.nix
index d8c6ea0f721b5..293dc43f91f38 100644
--- a/nixos/tests/fontconfig-default-fonts.nix
+++ b/nixos/tests/fontconfig-default-fonts.nix
@@ -9,7 +9,7 @@ import ./make-test-python.nix ({ lib, ... }:
   nodes.machine = { config, pkgs, ... }: {
     fonts.enableDefaultPackages = true; # Background fonts
     fonts.packages = with pkgs; [
-      noto-fonts-emoji
+      noto-fonts-color-emoji
       cantarell-fonts
       twitter-color-emoji
       source-code-pro
diff --git a/nixos/tests/forgejo.nix b/nixos/tests/forgejo.nix
new file mode 100644
index 0000000000000..6acd6acb50fa9
--- /dev/null
+++ b/nixos/tests/forgejo.nix
@@ -0,0 +1,178 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  ## gpg --faked-system-time='20230301T010000!' --quick-generate-key snakeoil ed25519 sign
+  signingPrivateKey = ''
+    -----BEGIN PGP PRIVATE KEY BLOCK-----
+
+    lFgEY/6jkBYJKwYBBAHaRw8BAQdADXiZRV8RJUyC9g0LH04wLMaJL9WTc+szbMi7
+    5fw4yP8AAQCl8EwGfzSLm/P6fCBfA3I9znFb3MEHGCCJhJ6VtKYyRw7ktAhzbmFr
+    ZW9pbIiUBBMWCgA8FiEE+wUM6VW/NLtAdSixTWQt6LZ4x50FAmP+o5ACGwMFCQPC
+    ZwAECwkIBwQVCgkIBRYCAwEAAh4FAheAAAoJEE1kLei2eMedFTgBAKQs1oGFZrCI
+    TZP42hmBTKxGAI1wg7VSdDEWTZxut/2JAQDGgo2sa4VHMfj0aqYGxrIwfP2B7JHO
+    GCqGCRf9O/hzBA==
+    =9Uy3
+    -----END PGP PRIVATE KEY BLOCK-----
+  '';
+  signingPrivateKeyId = "4D642DE8B678C79D";
+
+  supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
+  makeGForgejoTest = type: nameValuePair type (makeTest {
+    name = "forgejo-${type}";
+    meta.maintainers = with maintainers; [ bendlas emilylange ];
+
+    nodes = {
+      server = { config, pkgs, ... }: {
+        virtualisation.memorySize = 2047;
+        services.forgejo = {
+          enable = true;
+          database = { inherit type; };
+          settings.service.DISABLE_REGISTRATION = true;
+          settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
+          settings.actions.ENABLED = true;
+        };
+        environment.systemPackages = [ config.services.forgejo.package pkgs.gnupg pkgs.jq pkgs.file ];
+        services.openssh.enable = true;
+
+        specialisation.runner = {
+          inheritParentConfig = true;
+          configuration.services.gitea-actions-runner.instances."test" = {
+            enable = true;
+            name = "ci";
+            url = "http://localhost:3000";
+            labels = [
+              # don't require docker/podman
+              "native:host"
+            ];
+            tokenFile = "/var/lib/forgejo/runner_token";
+          };
+        };
+        specialisation.dump = {
+          inheritParentConfig = true;
+          configuration.services.forgejo.dump = {
+            enable = true;
+            type = "tar.zst";
+            file = "dump.tar.zst";
+          };
+        };
+      };
+      client1 = { config, pkgs, ... }: {
+        environment.systemPackages = [ pkgs.git ];
+      };
+      client2 = { config, pkgs, ... }: {
+        environment.systemPackages = [ pkgs.git ];
+      };
+    };
+
+    testScript = { nodes, ... }:
+      let
+        inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+        serverSystem = nodes.server.system.build.toplevel;
+        dumpFile = with nodes.server.specialisation.dump.configuration.services.forgejo.dump; "${backupDir}/${file}";
+      in
+      ''
+        import json
+        GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no"
+        REPO = "forgejo@server:test/repo"
+        PRIVK = "${snakeOilPrivateKey}"
+
+        start_all()
+
+        client1.succeed("mkdir /tmp/repo")
+        client1.succeed("mkdir -p $HOME/.ssh")
+        client1.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
+        client1.succeed("chmod 0400 $HOME/.ssh/privk")
+        client1.succeed("git -C /tmp/repo init")
+        client1.succeed("echo hello world > /tmp/repo/testfile")
+        client1.succeed("git -C /tmp/repo add .")
+        client1.succeed("git config --global user.email test@localhost")
+        client1.succeed("git config --global user.name test")
+        client1.succeed("git -C /tmp/repo commit -m 'Initial import'")
+        client1.succeed(f"git -C /tmp/repo remote add origin {REPO}")
+
+        server.wait_for_unit("forgejo.service")
+        server.wait_for_open_port(3000)
+        server.wait_for_open_port(22)
+        server.succeed("curl --fail http://localhost:3000/")
+
+        server.succeed(
+            "su -l forgejo -c 'gpg --homedir /var/lib/forgejo/data/home/.gnupg "
+            + "--import ${toString (pkgs.writeText "forgejo.key" signingPrivateKey)}'"
+        )
+
+        assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
+
+        server.succeed(
+            "curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. "
+            + "Please contact your site administrator.'"
+        )
+        server.succeed(
+            "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea admin user create "
+            + "--username test --password totallysafe --email test@localhost'"
+        )
+
+        api_token = server.succeed(
+            "curl --fail -X POST http://test:totallysafe@localhost:3000/api/v1/users/test/tokens "
+            + "-H 'Accept: application/json' -H 'Content-Type: application/json' -d "
+            + "'{\"name\":\"token\",\"scopes\":[\"all\"]}' | jq '.sha1' | xargs echo -n"
+        )
+
+        server.succeed(
+            "curl --fail -X POST http://localhost:3000/api/v1/user/repos "
+            + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
+            + f"-H 'Authorization: token {api_token}'"
+            + ' -d \'{"auto_init":false, "description":"string", "license":"mit", "name":"repo", "private":false}\'''
+        )
+
+        server.succeed(
+            "curl --fail -X POST http://localhost:3000/api/v1/user/keys "
+            + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
+            + f"-H 'Authorization: token {api_token}'"
+            + ' -d \'{"key":"${snakeOilPublicKey}","read_only":true,"title":"SSH"}\'''
+        )
+
+        client1.succeed(
+            f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git -C /tmp/repo push origin master"
+        )
+
+        client2.succeed("mkdir -p $HOME/.ssh")
+        client2.succeed(f"cat {PRIVK} > $HOME/.ssh/privk")
+        client2.succeed("chmod 0400 $HOME/.ssh/privk")
+        client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git clone {REPO}")
+        client2.succeed('test "$(cat repo/testfile | xargs echo -n)" = "hello world"')
+
+        with subtest("Testing git protocol version=2 over ssh"):
+            git_protocol = client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' GIT_TRACE2_EVENT=true git -C repo fetch |& grep negotiated-version")
+            version = json.loads(git_protocol).get("value")
+            assert version == "2", f"git did not negotiate protocol version 2, but version {version} instead."
+
+        server.wait_until_succeeds(
+            'test "$(curl http://localhost:3000/api/v1/repos/test/repo/commits '
+            + '-H "Accept: application/json" | jq length)" = "1"',
+            timeout=10
+        )
+
+        with subtest("Testing runner registration"):
+            server.succeed(
+                "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/forgejo/runner_token"
+            )
+            server.succeed("${serverSystem}/specialisation/runner/bin/switch-to-configuration test")
+            server.wait_for_unit("gitea-runner-test.service")
+            server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'")
+
+        with subtest("Testing backup service"):
+            server.succeed("${serverSystem}/specialisation/dump/bin/switch-to-configuration test")
+            server.systemctl("start forgejo-dump")
+            assert "Zstandard compressed data" in server.succeed("file ${dumpFile}")
+            server.copy_from_vm("${dumpFile}")
+      '';
+  });
+in
+
+listToAttrs (map makeGForgejoTest supportedDbTypes)
diff --git a/nixos/tests/freetube.nix b/nixos/tests/freetube.nix
new file mode 100644
index 0000000000000..f285384b68e0a
--- /dev/null
+++ b/nixos/tests/freetube.nix
@@ -0,0 +1,41 @@
+let
+  tests = {
+    wayland = { pkgs, ... }: {
+      imports = [ ./common/wayland-cage.nix ];
+      services.cage.program = "${pkgs.freetube}/bin/freetube";
+      virtualisation.memorySize = 2047;
+      environment.variables.NIXOS_OZONE_WL = "1";
+      environment.variables.DISPLAY = "do not use";
+    };
+    xorg = { pkgs, ... }: {
+      imports = [ ./common/user-account.nix ./common/x11.nix ];
+      virtualisation.memorySize = 2047;
+      services.xserver.enable = true;
+      services.xserver.displayManager.sessionCommands = ''
+        ${pkgs.freetube}/bin/freetube
+      '';
+      test-support.displayManager.auto.user = "alice";
+    };
+  };
+
+  mkTest = name: machine:
+    import ./make-test-python.nix ({ pkgs, ... }: {
+      inherit name;
+      nodes = { "${name}" = machine; };
+      meta.maintainers = with pkgs.lib.maintainers; [ kirillrdy ];
+      enableOCR = true;
+
+      testScript = ''
+        start_all()
+        machine.wait_for_unit('graphical.target')
+        machine.wait_for_text('Your Subscription list is currently empty')
+        machine.send_key("ctrl-r")
+        machine.wait_for_text('Your Subscription list is currently empty')
+        machine.screenshot("main.png")
+        machine.send_key("ctrl-comma")
+        machine.wait_for_text('General Settings', timeout=30)
+        machine.screenshot("preferences.png")
+      '';
+    });
+in
+builtins.mapAttrs (k: v: mkTest k v { }) tests
diff --git a/nixos/tests/freshrss-pgsql.nix b/nixos/tests/freshrss-pgsql.nix
index 055bd51ed43d7..c685f4a8159b8 100644
--- a/nixos/tests/freshrss-pgsql.nix
+++ b/nixos/tests/freshrss-pgsql.nix
@@ -22,9 +22,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       ensureUsers = [
         {
           name = "freshrss";
-          ensurePermissions = {
-            "DATABASE freshrss" = "ALL PRIVILEGES";
-          };
+          ensureDBOwnership = true;
         }
       ];
       initialScript = pkgs.writeText "postgresql-password" ''
diff --git a/nixos/tests/frp.nix b/nixos/tests/frp.nix
new file mode 100644
index 0000000000000..1f57c031a53a5
--- /dev/null
+++ b/nixos/tests/frp.nix
@@ -0,0 +1,85 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "frp";
+  meta.maintainers = with lib.maintainers; [ zaldnoay janik ];
+  nodes = {
+    frps = {
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.1/24";
+      };
+
+      services.frp = {
+        enable = true;
+        role = "server";
+        settings = {
+          bindPort = 7000;
+          vhostHTTPPort = 80;
+        };
+      };
+    };
+
+
+    frpc = {
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.2/24";
+      };
+
+      services.httpd = {
+        enable = true;
+        adminAddr = "admin@example.com";
+        virtualHosts."test-appication" =
+        let
+          testdir = pkgs.writeTextDir "web/index.php" "<?php phpinfo();";
+        in
+        {
+          documentRoot = "${testdir}/web";
+          locations."/" = {
+            index = "index.php index.html";
+          };
+        };
+        phpPackage = pkgs.php81;
+        enablePHP = true;
+      };
+
+      services.frp = {
+        enable = true;
+        role = "client";
+        settings = {
+          serverAddr = "10.0.0.1";
+          serverPort = 7000;
+          proxies = [
+            {
+              name = "web";
+              type = "http";
+              localPort = 80;
+              customDomains = [ "10.0.0.1" ];
+            }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    frps.wait_for_unit("frp.service")
+    frps.wait_for_open_port(80)
+    frpc.wait_for_unit("frp.service")
+    response = frpc.succeed("curl -fvvv -s http://127.0.0.1/")
+    assert "PHP Version ${pkgs.php81.version}" in response, "PHP version not detected"
+    response = frpc.succeed("curl -fvvv -s http://10.0.0.1/")
+    assert "PHP Version ${pkgs.php81.version}" in response, "PHP version not detected"
+  '';
+})
diff --git a/nixos/tests/frr.nix b/nixos/tests/frr.nix
index 598d7a7d28675..0d1a6a694a82c 100644
--- a/nixos/tests/frr.nix
+++ b/nixos/tests/frr.nix
@@ -29,7 +29,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       name = "frr";
 
       meta = with pkgs.lib.maintainers; {
-        maintainers = [ hexa ];
+        maintainers = [ ];
       };
 
       nodes = {
diff --git a/nixos/tests/ft2-clone.nix b/nixos/tests/ft2-clone.nix
index a8395d4ebaa62..5476b38c00bd2 100644
--- a/nixos/tests/ft2-clone.nix
+++ b/nixos/tests/ft2-clone.nix
@@ -4,12 +4,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ fgaz ];
   };
 
-  nodes.machine = { config, pkgs, ... }: {
+  nodes.machine = { pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
 
-    services.xserver.enable = true;
     sound.enable = true;
     environment.systemPackages = [ pkgs.ft2-clone ];
   };
@@ -30,4 +29,3 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       machine.screenshot("screen")
     '';
 })
-
diff --git a/nixos/tests/garage/basic.nix b/nixos/tests/garage/basic.nix
index b6df1e72af983..88d747ea33b9f 100644
--- a/nixos/tests/garage/basic.nix
+++ b/nixos/tests/garage/basic.nix
@@ -1,4 +1,4 @@
-args@{ mkNode, ... }:
+args@{ mkNode, ver, ... }:
 (import ../make-test-python.nix ({ pkgs, ...} : {
   name = "garage-basic";
   meta = {
@@ -52,7 +52,7 @@ args@{ mkNode, ... }:
        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}")
+       output = machine.succeed(f"garage key ${if ver == "0_8" then "new --name" else "create"} {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')
@@ -90,7 +90,7 @@ args@{ mkNode, ... }:
       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}"'])
+      apply_garage_layout(single_node, [f'-z qemutest -c ${if ver == "0_8" then "1" else "1G"} "{single_node_id}"'])
       # Now Garage is operational.
       test_bucket_writes(single_node)
       test_bucket_over_http(single_node)
diff --git a/nixos/tests/garage/default.nix b/nixos/tests/garage/default.nix
index 0a1ccde056b28..a42236e9a5bbe 100644
--- a/nixos/tests/garage/default.nix
+++ b/nixos/tests/garage/default.nix
@@ -44,10 +44,11 @@ let
 in
   foldl
   (matrix: ver: matrix // {
-    "basic${toString ver}" = import ./basic.nix { inherit system pkgs; mkNode = mkNode pkgs."garage_${ver}"; };
-    "with-3node-replication${toString ver}" = import ./with-3node-replication.nix { inherit system pkgs; mkNode = mkNode pkgs."garage_${ver}"; };
+    "basic${toString ver}" = import ./basic.nix { inherit system pkgs ver; mkNode = mkNode pkgs."garage_${ver}"; };
+    "with-3node-replication${toString ver}" = import ./with-3node-replication.nix { inherit system pkgs ver; mkNode = mkNode pkgs."garage_${ver}"; };
   })
   {}
   [
     "0_8"
+    "0_9"
   ]
diff --git a/nixos/tests/garage/with-3node-replication.nix b/nixos/tests/garage/with-3node-replication.nix
index d372ad1aa000f..d4387b198d976 100644
--- a/nixos/tests/garage/with-3node-replication.nix
+++ b/nixos/tests/garage/with-3node-replication.nix
@@ -1,4 +1,4 @@
-args@{ mkNode, ... }:
+args@{ mkNode, ver, ... }:
 (import ../make-test-python.nix ({ pkgs, ...} :
 {
   name = "garage-3node-replication";
@@ -55,7 +55,7 @@ args@{ mkNode, ... }:
        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}")
+       output = machine.succeed(f"garage key ${if ver == "0_8" then "new --name" else "create"} {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')
@@ -110,7 +110,7 @@ args@{ mkNode, ... }:
       zones = ["nixcon", "nixcon", "paris_meetup", "fosdem"]
       apply_garage_layout(node1,
       [
-        f'{ndata.node_id} -z {zones[index]} -c 1'
+        f'{ndata.node_id} -z {zones[index]} -c ${if ver == "0_8" then "1" else "1G"}'
         for index, ndata in enumerate(node_ids.values())
       ])
       # Now Garage is operational.
diff --git a/nixos/tests/geoserver.nix b/nixos/tests/geoserver.nix
new file mode 100644
index 0000000000000..7e5507a296eaf
--- /dev/null
+++ b/nixos/tests/geoserver.nix
@@ -0,0 +1,24 @@
+{ pkgs, lib, ... }: {
+
+  name = "geoserver";
+  meta = {
+    maintainers = with lib; [ teams.geospatial.members ];
+  };
+
+  nodes = {
+    machine = { pkgs, ... }: {
+      virtualisation.diskSize = 2 * 1024;
+
+      environment.systemPackages = [ pkgs.geoserver ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.execute("${pkgs.geoserver}/bin/geoserver-startup > /dev/null 2>&1 &")
+    machine.wait_until_succeeds("curl --fail --connect-timeout 2 http://localhost:8080/geoserver", timeout=60)
+
+    machine.succeed("curl --fail --connect-timeout 2 http://localhost:8080/geoserver/ows?service=WMS&version=1.3.0&request=GetCapabilities")
+  '';
+}
diff --git a/nixos/tests/gitdaemon.nix b/nixos/tests/gitdaemon.nix
index bb07b6e97b7fb..052fa902b4504 100644
--- a/nixos/tests/gitdaemon.nix
+++ b/nixos/tests/gitdaemon.nix
@@ -59,6 +59,9 @@ in {
     with subtest("git daemon starts"):
         server.wait_for_unit("git-daemon.service")
 
+
+    server.systemctl("start network-online.target")
+    client.systemctl("start network-online.target")
     server.wait_for_unit("network-online.target")
     client.wait_for_unit("network-online.target")
 
diff --git a/nixos/tests/gitea.nix b/nixos/tests/gitea.nix
index b747659de8298..f62c72bddddca 100644
--- a/nixos/tests/gitea.nix
+++ b/nixos/tests/gitea.nix
@@ -26,7 +26,7 @@ let
   supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
   makeGiteaTest = type: nameValuePair type (makeTest {
     name = "${giteaPackage.pname}-${type}";
-    meta.maintainers = with maintainers; [ aanderse emilylange kolaente ma27 ];
+    meta.maintainers = with maintainers; [ aanderse kolaente ma27 ];
 
     nodes = {
       server = { config, pkgs, ... }: {
@@ -35,9 +35,11 @@ let
           enable = true;
           database = { inherit type; };
           package = giteaPackage;
+          metricsTokenFile = (pkgs.writeText "metrics_secret" "fakesecret").outPath;
           settings.service.DISABLE_REGISTRATION = true;
           settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
           settings.actions.ENABLED = true;
+          settings.metrics.ENABLED = true;
         };
         environment.systemPackages = [ giteaPackage pkgs.gnupg pkgs.jq ];
         services.openssh.enable = true;
@@ -143,6 +145,12 @@ let
           + '-H "Accept: application/json" | jq length)" = "1"'
       )
 
+      with subtest("Testing metrics endpoint"):
+          server.succeed('curl '
+                         + '-H "Authorization: Bearer fakesecret" '
+                         + 'http://localhost:3000/metrics '
+                         + '| grep gitea_accesses')
+
       with subtest("Testing runner registration"):
           server.succeed(
               "su -l gitea -c 'GITEA_WORK_DIR=/var/lib/gitea gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/gitea/runner_token"
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index 88cd774f815a5..8d31264253119 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -34,7 +34,7 @@ in {
     gitlab = { ... }: {
       imports = [ common/user-account.nix ];
 
-      virtualisation.memorySize = if pkgs.stdenv.is64bit then 4096 else 2047;
+      virtualisation.memorySize = 6144;
       virtualisation.cores = 4;
       virtualisation.useNixStoreImage = true;
       virtualisation.writableStore = false;
diff --git a/nixos/tests/gnome-extensions.nix b/nixos/tests/gnome-extensions.nix
new file mode 100644
index 0000000000000..2faff9a4a80d5
--- /dev/null
+++ b/nixos/tests/gnome-extensions.nix
@@ -0,0 +1,151 @@
+import ./make-test-python.nix (
+{ pkgs, lib, ...}:
+{
+  name = "gnome-extensions";
+  meta.maintainers = [ lib.maintainers.piegames ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    {
+      imports = [ ./common/user-account.nix ];
+
+      # Install all extensions
+      environment.systemPackages = lib.filter (e: e ? extensionUuid) (lib.attrValues pkgs.gnomeExtensions);
+
+      # Some extensions are broken, but that's kind of the point of a testing VM
+      nixpkgs.config.allowBroken = true;
+      # There are some aliases which throw exceptions; ignore them.
+      # Also prevent duplicate extensions under different names.
+      nixpkgs.config.allowAliases = false;
+
+      # Configure GDM
+      services.xserver.enable = true;
+      services.xserver.displayManager = {
+        gdm = {
+          enable = true;
+          debug = true;
+          wayland = true;
+        };
+        autoLogin = {
+          enable = true;
+          user = "alice";
+        };
+      };
+
+      # Configure Gnome
+      services.xserver.desktopManager.gnome.enable = true;
+      services.xserver.desktopManager.gnome.debug = true;
+
+      systemd.user.services = {
+        "org.gnome.Shell@wayland" = {
+          serviceConfig = {
+            ExecStart = [
+              # Clear the list before overriding it.
+              ""
+              # Eval API is now internal so Shell needs to run in unsafe mode.
+              # TODO: improve test driver so that it supports openqa-like manipulation
+              # that would allow us to drop this mess.
+              "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode"
+            ];
+          };
+        };
+      };
+
+    };
+
+  testScript = { nodes, ... }: let
+    # Keep line widths somewhat manageable
+    user = nodes.machine.users.users.alice;
+    uid = toString user.uid;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
+
+    # Call javascript in gnome 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 = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
+
+    # False when startup is done
+    startingUp = eval "Main.layoutManager._startingUp";
+
+    # Extensions to keep always enabled together
+    # Those are extensions that are usually always on for many users, and that we expect to work
+    # well together with most others without conflicts
+    alwaysOnExtensions = map (name: pkgs.gnomeExtensions.${name}.extensionUuid) [
+      "applications-menu"
+      "user-themes"
+    ];
+
+    # Extensions to enable and disable individually
+    # Extensions like dash-to-dock and dash-to-panel cannot be enabled at the same time.
+    testExtensions = map (name: pkgs.gnomeExtensions.${name}.extensionUuid) [
+      "appindicator"
+      "dash-to-dock"
+      "dash-to-panel"
+      "ddterm"
+      "emoji-selector"
+      "gsconnect"
+      "system-monitor"
+      "desktop-icons-ng-ding"
+      "workspace-indicator"
+      "vitals"
+    ];
+  in
+  ''
+    with subtest("Login to GNOME with GDM"):
+        # wait for gdm to start
+        machine.wait_for_unit("display-manager.service")
+        # wait for the wayland server
+        machine.wait_for_file("/run/user/${uid}/wayland-0")
+        # wait for alice to be logged in
+        machine.wait_for_unit("default.target", "${user.name}")
+        # check that logging in has given the user ownership of devices
+        assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
+
+    with subtest("Wait for GNOME Shell"):
+        # correct output should be (true, 'false')
+        machine.wait_until_succeeds(
+            "${startingUp} | grep -q 'true,..false'"
+        )
+
+        # Close the Activities view so that Shell can correctly track the focused window.
+        machine.send_key("esc")
+        # # Disable extension version validation (only use for manual testing)
+        # machine.succeed(
+        #   "${run "gsettings set org.gnome.shell disable-extension-version-validation true"}"
+        # )
+
+    # Assert that some extension is in a specific state
+    def checkState(target, extension):
+        state = machine.succeed(
+            f"${run "gnome-extensions info {extension}"} | grep '^  State: .*$'"
+        )
+        assert target in state, f"{state} instead of {target}"
+
+    def checkExtension(extension, disable):
+        with subtest(f"Enable extension '{extension}'"):
+            # Check that the extension is properly initialized; skip out of date ones
+            state = machine.succeed(
+                f"${run "gnome-extensions info {extension}"} | grep '^  State: .*$'"
+            )
+            if "OUT OF DATE" in state:
+                machine.log(f"Extension {extension} will be skipped because out of date")
+                return
+
+            assert "INITIALIZED" in state, f"{state} instead of INITIALIZED"
+
+            # Enable and optionally disable
+
+            machine.succeed(f"${run "gnome-extensions enable {extension}"}")
+            checkState("ENABLED", extension)
+
+            if disable:
+                machine.succeed(f"${run "gnome-extensions disable {extension}"}")
+                checkState("DISABLED", extension)
+    ''
+    + lib.concatLines (map (e: ''checkExtension("${e}", False)'') alwaysOnExtensions)
+    + lib.concatLines (map (e: ''checkExtension("${e}", True)'') testExtensions)
+  ;
+}
+)
diff --git a/nixos/tests/gnome-flashback.nix b/nixos/tests/gnome-flashback.nix
index 876d36477c1a9..f486dabc5c40b 100644
--- a/nixos/tests/gnome-flashback.nix
+++ b/nixos/tests/gnome-flashback.nix
@@ -32,14 +32,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     xauthority = "/run/user/${uid}/gdm/Xauthority";
   in ''
       with subtest("Login to GNOME Flashback with GDM"):
-          # wait_for_x() checks graphical-session.target, which is expected to be
-          # inactive on gnome-flashback before #228946 (i.e. systemd managed
-          # gnome-session) is done.
-          # https://github.com/NixOS/nixpkgs/pull/208060
-          #
-          # Previously this was unconditionally touched by xsessionWrapper but was
-          # changed in #233981 (we have GNOME-Flashback:GNOME in XDG_CURRENT_DESKTOP).
-          # machine.wait_for_x()
+          machine.wait_for_x()
           machine.wait_until_succeeds('journalctl -t gnome-session-binary --grep "Entering running state"')
           # Wait for alice to be logged in"
           machine.wait_for_unit("default.target", "${user.name}")
@@ -49,9 +42,10 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
           assert "alice" in machine.succeed("getfacl -p /dev/snd/timer")
 
       with subtest("Wait for Metacity"):
-          machine.wait_until_succeeds(
-              "pgrep metacity"
-          )
+          machine.wait_until_succeeds("pgrep metacity")
+
+      with subtest("Regression test for #233920"):
+          machine.wait_until_succeeds("pgrep -fa gnome-flashback-media-keys")
           machine.sleep(20)
           machine.screenshot("screen")
     '';
diff --git a/nixos/tests/gnome-xorg.nix b/nixos/tests/gnome-xorg.nix
index 7762fff5c3a25..6ca700edcac38 100644
--- a/nixos/tests/gnome-xorg.nix
+++ b/nixos/tests/gnome-xorg.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
   };
 
   nodes.machine = { nodes, ... }: let
-    user = nodes.machine.config.users.users.alice;
+    user = nodes.machine.users.users.alice;
   in
 
     { imports = [ ./common/user-account.nix ];
@@ -43,28 +43,28 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
     };
 
   testScript = { nodes, ... }: let
-    user = nodes.machine.config.users.users.alice;
+    user = nodes.machine.users.users.alice;
     uid = toString user.uid;
     bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
     xauthority = "/run/user/${uid}/gdm/Xauthority";
     display = "DISPLAY=:0.0";
     env = "${bus} XAUTHORITY=${xauthority} ${display}";
-    gdbus = "${env} gdbus";
-    su = command: "su - ${user.name} -c '${env} ${command}'";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
 
     # Call javascript in gnome 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.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval";
+    eval = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
 
     # False when startup is done
-    startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp";
+    startingUp = eval "Main.layoutManager._startingUp";
 
     # Start Console
-    launchConsole = su "${bus} gapplication launch org.gnome.Console";
+    launchConsole = run "gapplication launch org.gnome.Console";
 
     # Hopefully Console's wm class
-    wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class";
+    wmClass = eval "global.display.focus_window.wm_class";
   in ''
       with subtest("Login to GNOME Xorg with GDM"):
           machine.wait_for_x()
diff --git a/nixos/tests/gnome.nix b/nixos/tests/gnome.nix
index 448a3350240c7..91182790cb248 100644
--- a/nixos/tests/gnome.nix
+++ b/nixos/tests/gnome.nix
@@ -40,25 +40,25 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
   testScript = { nodes, ... }: let
     # Keep line widths somewhat manageable
-    user = nodes.machine.config.users.users.alice;
+    user = nodes.machine.users.users.alice;
     uid = toString user.uid;
     bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${uid}/bus";
-    gdbus = "${bus} gdbus";
-    su = command: "su - ${user.name} -c '${command}'";
+    # Run a command in the appropriate user environment
+    run = command: "su - ${user.name} -c '${bus} ${command}'";
 
     # Call javascript in gnome 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.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval";
+    eval = command: run "gdbus call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval ${command}";
 
     # False when startup is done
-    startingUp = su "${gdbus} ${eval} Main.layoutManager._startingUp";
+    startingUp = eval "Main.layoutManager._startingUp";
 
     # Start Console
-    launchConsole = su "${bus} gapplication launch org.gnome.Console";
+    launchConsole = run "gapplication launch org.gnome.Console";
 
     # Hopefully Console's wm class
-    wmClass = su "${gdbus} ${eval} global.display.focus_window.wm_class";
+    wmClass = eval "global.display.focus_window.wm_class";
   in ''
       with subtest("Login to GNOME with GDM"):
           # wait for gdm to start
diff --git a/nixos/tests/gns3-server.nix b/nixos/tests/gns3-server.nix
new file mode 100644
index 0000000000000..e37d751f5f640
--- /dev/null
+++ b/nixos/tests/gns3-server.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "gns3-server";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine =
+    { ... }:
+    let
+      tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+        openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -days 365 \
+          -subj '/CN=localhost'
+        install -D -t $out key.pem cert.pem
+      '';
+    in {
+      services.gns3-server = {
+        enable = true;
+        auth = {
+          enable = true;
+          user = "user";
+          passwordFile = pkgs.writeText "gns3-auth-password-file" "password";
+        };
+        ssl = {
+          enable = true;
+          certFile = "${tls-cert}/cert.pem";
+          keyFile = "${tls-cert}/key.pem";
+        };
+        dynamips.enable = true;
+        ubridge.enable = true;
+        vpcs.enable = true;
+      };
+
+      security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
+    };
+
+  testScript = let
+    createProject = pkgs.writeText "createProject.json" (builtins.toJSON {
+      name = "test_project";
+    });
+  in
+  ''
+    start_all()
+
+    machine.wait_for_unit("gns3-server.service")
+    machine.wait_for_open_port(3080)
+
+    with subtest("server is listening"):
+      machine.succeed("curl -sSfL -u user:password https://localhost:3080/v2/version")
+
+    with subtest("create dummy project"):
+      machine.succeed("curl -sSfL -u user:password https://localhost:3080/v2/projects -d @${createProject}")
+
+    with subtest("logging works"):
+      log_path = "/var/log/gns3/server.log"
+      machine.wait_for_file(log_path)
+  '';
+})
diff --git a/nixos/tests/google-oslogin/default.nix b/nixos/tests/google-oslogin/default.nix
index 72c87d7153bdf..cd05af6b9ed7a 100644
--- a/nixos/tests/google-oslogin/default.nix
+++ b/nixos/tests/google-oslogin/default.nix
@@ -12,7 +12,7 @@ let
 in {
   name = "google-oslogin";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ adisbladis flokli ];
+    maintainers = [ flokli ];
   };
 
   nodes = {
@@ -71,4 +71,3 @@ in {
     )
   '';
   })
-
diff --git a/nixos/tests/goss.nix b/nixos/tests/goss.nix
new file mode 100644
index 0000000000000..6b772d19215e3
--- /dev/null
+++ b/nixos/tests/goss.nix
@@ -0,0 +1,53 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "goss";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
+
+  nodes.machine = {
+    environment.systemPackages = [ pkgs.jq ];
+
+    services.goss = {
+      enable = true;
+
+      environment = {
+        GOSS_FMT = "json";
+      };
+
+      settings = {
+        addr."tcp://localhost:8080" = {
+          reachable = true;
+          local-address = "127.0.0.1";
+        };
+        command."check-goss-version" = {
+          exec = "${lib.getExe pkgs.goss} --version";
+          exit-status = 0;
+        };
+        dns.localhost.resolvable = true;
+        file."/nix" = {
+          filetype = "directory";
+          exists = true;
+        };
+        group.root.exists = true;
+        kernel-param."kernel.ostype".value = "Linux";
+        service.goss = {
+          enabled = true;
+          running = true;
+        };
+        user.root.exists = true;
+      };
+    };
+  };
+
+  testScript = ''
+    import json
+
+    machine.wait_for_unit("goss.service")
+    machine.wait_for_open_port(8080)
+
+    with subtest("returns health status"):
+      result = json.loads(machine.succeed("curl -sS http://localhost:8080/healthz"))
+
+      assert len(result["results"]) == 10, f".results should be an array of 10 items, was {result['results']!r}"
+      assert result["summary"]["failed-count"] == 0, f".summary.failed-count should be zero, was {result['summary']['failed-count']}"
+      assert result["summary"]["test-count"] == 10, f".summary.test-count should be 10, was {result['summary']['test-count']}"
+    '';
+})
diff --git a/nixos/tests/gotify-server.nix b/nixos/tests/gotify-server.nix
index d004f542b39a3..c8d7fa172a7b7 100644
--- a/nixos/tests/gotify-server.nix
+++ b/nixos/tests/gotify-server.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, lib, ...} : {
   name = "gotify-server";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ma27 ];
+    maintainers = [ ];
   };
 
   nodes.machine = { pkgs, ... }: {
diff --git a/nixos/tests/grafana/basic.nix b/nixos/tests/grafana/basic.nix
index 8bf4caad7fbfc..dd389bc8a3d1f 100644
--- a/nixos/tests/grafana/basic.nix
+++ b/nixos/tests/grafana/basic.nix
@@ -55,7 +55,7 @@ let
         ensureDatabases = [ "grafana" ];
         ensureUsers = [{
           name = "grafana";
-          ensurePermissions."DATABASE grafana" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }];
       };
       systemd.services.grafana.after = [ "postgresql.service" ];
diff --git a/nixos/tests/grafana/provision/default.nix b/nixos/tests/grafana/provision/default.nix
index 96378452ade31..d33d16ce12099 100644
--- a/nixos/tests/grafana/provision/default.nix
+++ b/nixos/tests/grafana/provision/default.nix
@@ -22,15 +22,14 @@ let
       };
     };
 
-    system.activationScripts.setup-grafana = {
-      deps = [ "users" ];
-      text = ''
-        mkdir -p /var/lib/grafana/dashboards
-        chown -R grafana:grafana /var/lib/grafana
-        chmod 0700 -R /var/lib/grafana/dashboards
-        cp ${pkgs.writeText "test.json" (builtins.readFile ./test_dashboard.json)} /var/lib/grafana/dashboards/
-      '';
-    };
+    systemd.tmpfiles.rules =
+      let
+        dashboard = pkgs.writeText "test.json" (builtins.readFile ./test_dashboard.json);
+      in
+      [
+        "d /var/lib/grafana/dashboards 0700 grafana grafana -"
+        "C+ /var/lib/grafana/dashboards/test.json - - - - ${dashboard}"
+      ];
   };
 
   extraNodeConfs = {
diff --git a/nixos/tests/grow-partition.nix b/nixos/tests/grow-partition.nix
new file mode 100644
index 0000000000000..344910848dca8
--- /dev/null
+++ b/nixos/tests/grow-partition.nix
@@ -0,0 +1,83 @@
+{ lib, ... }:
+
+let
+  rootFslabel = "external";
+  rootFsDevice = "/dev/disk/by-label/${rootFslabel}";
+
+  externalModule = partitionTableType: { config, lib, pkgs, ... }: {
+    virtualisation.directBoot.enable = false;
+    virtualisation.mountHostNixStore = false;
+    virtualisation.useEFIBoot = partitionTableType == "efi";
+
+    # This stops the qemu-vm module from overriding the fileSystems option
+    # with virtualisation.fileSystems.
+    virtualisation.fileSystems = lib.mkForce { };
+
+
+    boot.loader.grub.enable = true;
+    boot.loader.grub.efiSupport = partitionTableType == "efi";
+    boot.loader.grub.efiInstallAsRemovable = partitionTableType == "efi";
+    boot.loader.grub.device = if partitionTableType == "efi" then "nodev" else "/dev/vda";
+
+    boot.growPartition = true;
+
+    fileSystems = {
+      "/".device = rootFsDevice;
+    };
+
+    system.build.diskImage = import ../lib/make-disk-image.nix {
+      inherit config lib pkgs;
+      label = rootFslabel;
+      inherit partitionTableType;
+      format = "raw";
+      bootSize = "128M";
+      additionalSpace = "0M";
+      copyChannel = false;
+    };
+  };
+in
+{
+  name = "grow-partition";
+
+  meta.maintainers = with lib.maintainers; [ arianvp ];
+
+  nodes = {
+    efi = externalModule "efi";
+    legacy = externalModule "legacy";
+    legacyGPT = externalModule "legacy+gpt";
+    hybrid = externalModule "hybrid";
+  };
+
+
+  testScript = { nodes, ... }:
+    lib.concatLines (lib.mapAttrsToList (name: node: ''
+    import os
+    import subprocess
+    import tempfile
+    import shutil
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    shutil.copyfile("${node.system.build.diskImage}/nixos.img", tmp_disk_image.name)
+
+    subprocess.run([
+      "${node.virtualisation.qemu.package}/bin/qemu-img",
+      "resize",
+      "-f",
+      "raw",
+      tmp_disk_image.name,
+      "+32M",
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+
+    ${name}.wait_for_unit("growpart.service")
+    systemd_growpart_logs = ${name}.succeed("journalctl --boot --unit growpart.service")
+    assert "CHANGED" in systemd_growpart_logs
+    ${name}.succeed("systemctl restart growpart.service")
+    systemd_growpart_logs = ${name}.succeed("journalctl --boot --unit growpart.service")
+    assert "NOCHANGE" in systemd_growpart_logs
+
+    '') nodes);
+}
diff --git a/nixos/tests/guix/basic.nix b/nixos/tests/guix/basic.nix
new file mode 100644
index 0000000000000..9b943b8965e60
--- /dev/null
+++ b/nixos/tests/guix/basic.nix
@@ -0,0 +1,42 @@
+# Take note the Guix store directory is empty. Also, we're trying to prevent
+# Guix from trying to downloading substitutes because of the restricted
+# access (assuming it's in a sandboxed environment).
+#
+# So this test is what it is: a basic test while trying to use Guix as much as
+# we possibly can (including the API) without triggering its download alarm.
+
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "guix-basic";
+  meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
+
+  nodes.machine = { config, ... }: {
+    environment.etc."guix/scripts".source = ./scripts;
+    services.guix = {
+      enable = true;
+      gc.enable = true;
+    };
+  };
+
+  testScript = ''
+    import pathlib
+
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_for_unit("guix-daemon.service")
+    machine.succeed("systemctl start guix-gc.service")
+
+    # Can't do much here since the environment has restricted network access.
+    with subtest("Guix basic package management"):
+      machine.succeed("guix build --dry-run --verbosity=0 hello")
+      machine.succeed("guix show hello")
+
+    # This is to see if the Guix API is usable and mostly working.
+    with subtest("Guix API scripting"):
+      scripts_dir = pathlib.Path("/etc/guix/scripts")
+
+      text_msg = "Hello there, NixOS!"
+      text_store_file = machine.succeed(f"guix repl -- {scripts_dir}/create-file-to-store.scm '{text_msg}'")
+      assert machine.succeed(f"cat {text_store_file}") == text_msg
+
+      machine.succeed(f"guix repl -- {scripts_dir}/add-existing-files-to-store.scm {scripts_dir}")
+  '';
+})
diff --git a/nixos/tests/guix/default.nix b/nixos/tests/guix/default.nix
new file mode 100644
index 0000000000000..a017668c05a75
--- /dev/null
+++ b/nixos/tests/guix/default.nix
@@ -0,0 +1,8 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+
+{
+  basic = import ./basic.nix { inherit system pkgs; };
+  publish = import ./publish.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/guix/publish.nix b/nixos/tests/guix/publish.nix
new file mode 100644
index 0000000000000..eb56fc97478cc
--- /dev/null
+++ b/nixos/tests/guix/publish.nix
@@ -0,0 +1,96 @@
+# Testing out the substitute server with two machines in a local network. As a
+# bonus, we'll also test a feature of the substitute server being able to
+# advertise its service to the local network with Avahi.
+
+import ../make-test-python.nix ({ pkgs, lib, ... }: let
+  publishPort = 8181;
+  inherit (builtins) toString;
+in {
+  name = "guix-publish";
+
+  meta.maintainers = with lib.maintainers; [ foo-dogsquared ];
+
+  nodes = let
+    commonConfig = { config, ... }: {
+      # We'll be using '--advertise' flag with the
+      # substitute server which requires Avahi.
+      services.avahi = {
+        enable = true;
+        nssmdns4 = true;
+        publish = {
+          enable = true;
+          userServices = true;
+        };
+      };
+    };
+  in {
+    server = { config, lib, pkgs, ... }: {
+      imports = [ commonConfig ];
+
+      services.guix = {
+        enable = true;
+        publish = {
+          enable = true;
+          port = publishPort;
+
+          generateKeyPair = true;
+          extraArgs = [ "--advertise" ];
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ publishPort ];
+    };
+
+    client = { config, lib, pkgs, ... }: {
+      imports = [ commonConfig ];
+
+      services.guix = {
+        enable = true;
+
+        extraArgs = [
+          # Force to only get all substitutes from the local server. We don't
+          # have anything in the Guix store directory and we cannot get
+          # anything from the official substitute servers anyways.
+          "--substitute-urls='http://server.local:${toString publishPort}'"
+
+          # Enable autodiscovery of the substitute servers in the local
+          # network. This machine shouldn't need to import the signing key from
+          # the substitute server since it is automatically done anyways.
+          "--discover=yes"
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    import pathlib
+
+    start_all()
+
+    scripts_dir = pathlib.Path("/etc/guix/scripts")
+
+    for machine in machines:
+      machine.wait_for_unit("multi-user.target")
+      machine.wait_for_unit("guix-daemon.service")
+      machine.wait_for_unit("avahi-daemon.service")
+
+    server.wait_for_unit("guix-publish.service")
+    server.wait_for_open_port(${toString publishPort})
+    server.succeed("curl http://localhost:${toString publishPort}/")
+
+    # Now it's the client turn to make use of it.
+    substitute_server = "http://server.local:${toString publishPort}"
+    client.systemctl("start network-online.target")
+    client.wait_for_unit("network-online.target")
+    response = client.succeed(f"curl {substitute_server}")
+    assert "Guix Substitute Server" in response
+
+    # Authorizing the server to be used as a substitute server.
+    client.succeed(f"curl -O {substitute_server}/signing-key.pub")
+    client.succeed("guix archive --authorize < ./signing-key.pub")
+
+    # Since we're using the substitute server with the `--advertise` flag, we
+    # might as well check it.
+    client.succeed("avahi-browse --resolve --terminate _guix_publish._tcp | grep '_guix_publish._tcp'")
+  '';
+})
diff --git a/nixos/tests/guix/scripts/add-existing-files-to-store.scm b/nixos/tests/guix/scripts/add-existing-files-to-store.scm
new file mode 100644
index 0000000000000..fa47320b6a511
--- /dev/null
+++ b/nixos/tests/guix/scripts/add-existing-files-to-store.scm
@@ -0,0 +1,52 @@
+;; A simple script that adds each file given from the command-line into the
+;; store and checks them if it's the same.
+(use-modules (guix)
+             (srfi srfi-1)
+             (ice-9 ftw)
+             (rnrs io ports))
+
+;; This is based from tests/derivations.scm from Guix source code.
+(define* (directory-contents dir #:optional (slurp get-bytevector-all))
+         "Return an alist representing the contents of DIR"
+         (define prefix-len (string-length dir))
+         (sort (file-system-fold (const #t)
+                                 (lambda (path stat result)
+                                   (alist-cons (string-drop path prefix-len)
+                                               (call-with-input-file path slurp)
+                                               result))
+                                 (lambda (path stat result) result)
+                                 (lambda (path stat result) result)
+                                 (lambda (path stat result) result)
+                                 (lambda (path stat errno result) result)
+                                 '()
+                                 dir)
+               (lambda (e1 e2)
+                 (string<? (car e1) (car e2)))))
+
+(define* (check-if-same store drv path)
+         "Check if the given path and its store item are the same"
+         (let* ((filetype (stat:type (stat drv))))
+           (case filetype
+             ((regular)
+              (and (valid-path? store drv)
+                   (equal? (call-with-input-file path get-bytevector-all)
+                           (call-with-input-file drv get-bytevector-all))))
+             ((directory)
+              (and (valid-path? store drv)
+                   (equal? (directory-contents path)
+                           (directory-contents drv))))
+             (else #f))))
+
+(define* (add-and-check-item-to-store store path)
+         "Add PATH to STORE and check if the contents are the same"
+         (let* ((store-item (add-to-store store
+                                          (basename path)
+                                          #t "sha256" path))
+                (is-same (check-if-same store store-item path)))
+           (if (not is-same)
+             (exit 1))))
+
+(with-store store
+            (map (lambda (path)
+                   (add-and-check-item-to-store store (readlink* path)))
+                 (cdr (command-line))))
diff --git a/nixos/tests/guix/scripts/create-file-to-store.scm b/nixos/tests/guix/scripts/create-file-to-store.scm
new file mode 100644
index 0000000000000..467e4c4fd53f2
--- /dev/null
+++ b/nixos/tests/guix/scripts/create-file-to-store.scm
@@ -0,0 +1,8 @@
+;; A script that creates a store item with the given text and prints the
+;; resulting store item path.
+(use-modules (guix))
+
+(with-store store
+            (display (add-text-to-store store "guix-basic-test-text"
+                                        (string-join
+                                          (cdr (command-line))))))
diff --git a/nixos/tests/gvisor.nix b/nixos/tests/gvisor.nix
index 77ff29341bed2..7f130b709fc9d 100644
--- a/nixos/tests/gvisor.nix
+++ b/nixos/tests/gvisor.nix
@@ -1,6 +1,6 @@
 # This test runs a container through gvisor and checks if simple container starts
 
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "gvisor";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ andrew-d ];
@@ -9,21 +9,21 @@ import ./make-test-python.nix ({ pkgs, ...} : {
   nodes = {
     gvisor =
       { pkgs, ... }:
-        {
-          virtualisation.docker = {
-            enable = true;
-            extraOptions = "--add-runtime runsc=${pkgs.gvisor}/bin/runsc";
-          };
+      {
+        virtualisation.docker = {
+          enable = true;
+          extraOptions = "--add-runtime runsc=${pkgs.gvisor}/bin/runsc";
+        };
 
-          networking = {
-            dhcpcd.enable = false;
-            defaultGateway = "192.168.1.1";
-            interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
-              { address = "192.168.1.2"; prefixLength = 24; }
-            ];
-          };
+        networking = {
+          dhcpcd.enable = false;
+          defaultGateway = "192.168.1.1";
+          interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+            { address = "192.168.1.2"; prefixLength = 24; }
+          ];
         };
-    };
+      };
+  };
 
   testScript = ''
     start_all()
@@ -31,13 +31,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     gvisor.wait_for_unit("network.target")
     gvisor.wait_for_unit("sockets.target")
 
-    # Start by verifying that gvisor itself works
-    output = gvisor.succeed(
-        "${pkgs.gvisor}/bin/runsc -alsologtostderr do ${pkgs.coreutils}/bin/echo hello world"
-    )
-    assert output.strip() == "hello world"
-
-    # Also test the Docker runtime
+    # Test the Docker runtime
     gvisor.succeed("tar cv --files-from /dev/null | docker import - scratchimg")
     gvisor.succeed(
         "docker run -d --name=sleeping --runtime=runsc -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10"
diff --git a/nixos/tests/hadoop/hadoop.nix b/nixos/tests/hadoop/hadoop.nix
index b132f4fa58b01..6162ccfd33d47 100644
--- a/nixos/tests/hadoop/hadoop.nix
+++ b/nixos/tests/hadoop/hadoop.nix
@@ -176,22 +176,22 @@ import ../make-test-python.nix ({ package, ... }: {
     nn2.succeed("systemctl stop hdfs-zkfc")
 
     # Initialize zookeeper for failover controller
-    nn1.succeed("sudo -u hdfs hdfs zkfc -formatZK 2>&1 | systemd-cat")
+    nn1.succeed("sudo -u hdfs systemd-cat hdfs zkfc -formatZK")
 
     # Format NN1 and start it
-    nn1.succeed("sudo -u hdfs hadoop namenode -format 2>&1 | systemd-cat")
+    nn1.succeed("sudo -u hdfs systemd-cat hadoop namenode -format")
     nn1.succeed("systemctl start hdfs-namenode")
     nn1.wait_for_open_port(9870)
     nn1.wait_for_open_port(8022)
     nn1.wait_for_open_port(8020)
 
     # Bootstrap NN2 from NN1 and start it
-    nn2.succeed("sudo -u hdfs hdfs namenode -bootstrapStandby 2>&1 | systemd-cat")
+    nn2.succeed("sudo -u hdfs systemd-cat hdfs namenode -bootstrapStandby")
     nn2.succeed("systemctl start hdfs-namenode")
     nn2.wait_for_open_port(9870)
     nn2.wait_for_open_port(8022)
     nn2.wait_for_open_port(8020)
-    nn1.succeed("netstat -tulpne | systemd-cat")
+    nn1.succeed("systemd-cat netstat -tulpne")
 
     # Start failover controllers
     nn1.succeed("systemctl start hdfs-zkfc")
@@ -200,10 +200,10 @@ import ../make-test-python.nix ({ package, ... }: {
     # DN should have started by now, but confirm anyway
     dn1.wait_for_unit("hdfs-datanode")
     # Print states of namenodes
-    client.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState | systemd-cat")
+    client.succeed("sudo -u hdfs systemd-cat hdfs haadmin -getAllServiceState")
     # Wait for cluster to exit safemode
     client.succeed("sudo -u hdfs hdfs dfsadmin -safemode wait")
-    client.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState | systemd-cat")
+    client.succeed("sudo -u hdfs systemd-cat hdfs haadmin -getAllServiceState")
     # test R/W
     client.succeed("echo testfilecontents | sudo -u hdfs hdfs dfs -put - /testfile")
     assert "testfilecontents" in client.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
@@ -211,7 +211,7 @@ import ../make-test-python.nix ({ package, ... }: {
     # Test NN failover
     nn1.succeed("systemctl stop hdfs-namenode")
     assert "active" in client.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState")
-    client.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState | systemd-cat")
+    client.succeed("sudo -u hdfs systemd-cat hdfs haadmin -getAllServiceState")
     assert "testfilecontents" in client.succeed("sudo -u hdfs hdfs dfs -cat /testfile")
 
     nn1.succeed("systemctl start hdfs-namenode")
@@ -219,7 +219,7 @@ import ../make-test-python.nix ({ package, ... }: {
     nn1.wait_for_open_port(8022)
     nn1.wait_for_open_port(8020)
     assert "standby" in client.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState")
-    client.succeed("sudo -u hdfs hdfs haadmin -getAllServiceState | systemd-cat")
+    client.succeed("sudo -u hdfs systemd-cat hdfs haadmin -getAllServiceState")
 
     #### YARN tests ####
 
@@ -236,20 +236,20 @@ import ../make-test-python.nix ({ package, ... }: {
     nm1.wait_for_open_port(8042)
     nm1.wait_for_open_port(8040)
     client.wait_until_succeeds("yarn node -list | grep Nodes:1")
-    client.succeed("sudo -u yarn yarn rmadmin -getAllServiceState | systemd-cat")
-    client.succeed("sudo -u yarn yarn node -list | systemd-cat")
+    client.succeed("sudo -u yarn systemd-cat yarn rmadmin -getAllServiceState")
+    client.succeed("sudo -u yarn systemd-cat yarn node -list")
 
     # Test RM failover
     rm1.succeed("systemctl stop yarn-resourcemanager")
     assert "standby" not in client.succeed("sudo -u yarn yarn rmadmin -getAllServiceState")
-    client.succeed("sudo -u yarn yarn rmadmin -getAllServiceState | systemd-cat")
+    client.succeed("sudo -u yarn systemd-cat yarn rmadmin -getAllServiceState")
     rm1.succeed("systemctl start yarn-resourcemanager")
     rm1.wait_for_unit("yarn-resourcemanager")
     rm1.wait_for_open_port(8088)
     assert "standby" in client.succeed("sudo -u yarn yarn rmadmin -getAllServiceState")
-    client.succeed("sudo -u yarn yarn rmadmin -getAllServiceState | systemd-cat")
+    client.succeed("sudo -u yarn systemd-cat yarn rmadmin -getAllServiceState")
 
-    assert "Estimated value of Pi is" in client.succeed("HADOOP_USER_NAME=hdfs yarn jar $(readlink $(which yarn) | sed -r 's~bin/yarn~lib/hadoop-*/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar~g') pi 2 10")
+    assert "Estimated value of Pi is" in client.succeed("HADOOP_USER_NAME=hdfs yarn jar $(readlink $(which yarn) | sed -r 's~bin/yarn~share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar~g') pi 2 10")
     assert "SUCCEEDED" in client.succeed("yarn application -list -appStates FINISHED")
   '';
 })
diff --git a/nixos/tests/hadoop/hdfs.nix b/nixos/tests/hadoop/hdfs.nix
index 429d4bf6b5389..65686b3715598 100644
--- a/nixos/tests/hadoop/hdfs.nix
+++ b/nixos/tests/hadoop/hdfs.nix
@@ -50,8 +50,8 @@ import ../make-test-python.nix ({ package, lib, ... }:
     namenode.wait_for_unit("hdfs-namenode")
     namenode.wait_for_unit("network.target")
     namenode.wait_for_open_port(8020)
-    namenode.succeed("ss -tulpne | systemd-cat")
-    namenode.succeed("cat /etc/hadoop*/hdfs-site.xml | systemd-cat")
+    namenode.succeed("systemd-cat ss -tulpne")
+    namenode.succeed("systemd-cat cat /etc/hadoop*/hdfs-site.xml")
     namenode.wait_for_open_port(9870)
 
     datanode.wait_for_unit("hdfs-datanode")
diff --git a/nixos/tests/haproxy.nix b/nixos/tests/haproxy.nix
index 555474d7f2999..1730938737577 100644
--- a/nixos/tests/haproxy.nix
+++ b/nixos/tests/haproxy.nix
@@ -1,22 +1,42 @@
-import ./make-test-python.nix ({ pkgs, ...}: {
+import ./make-test-python.nix ({ lib, pkgs, ...}: {
   name = "haproxy";
   nodes = {
-    machine = { ... }: {
-      services.haproxy = {
+    server = { ... }: {
+     services.haproxy = {
         enable = true;
         config = ''
+          global
+            limited-quic
+
           defaults
+            mode http
             timeout connect 10s
+            timeout client 10s
+            timeout server 10s
+
+            log /dev/log local0 debug err
+            option logasap
+            option httplog
+            option httpslog
 
           backend http_server
-            mode http
-            server httpd [::1]:8000
+            server httpd [::1]:8000 alpn http/1.1
 
           frontend http
-            bind *:80
-            mode http
+            bind :80
+            bind :443 ssl strict-sni crt /etc/ssl/fullchain.pem alpn h2,http/1.1
+            bind quic4@:443 ssl strict-sni crt /etc/ssl/fullchain.pem alpn h3 allow-0rtt
+
+            http-after-response add-header alt-svc 'h3=":443"; ma=60' if { ssl_fc }
+
             http-request use-service prometheus-exporter if { path /metrics }
             use_backend http_server
+
+          frontend http-cert-auth
+            bind :8443 ssl strict-sni crt /etc/ssl/fullchain.pem verify required ca-file /etc/ssl/cacert.crt
+            bind quic4@:8443 ssl strict-sni crt /etc/ssl/fullchain.pem verify required ca-file /etc/ssl/cacert.crt alpn h3
+
+            use_backend http_server
         '';
       };
       services.httpd = {
@@ -30,24 +50,75 @@ import ./make-test-python.nix ({ pkgs, ...}: {
           }];
         };
       };
+      networking.firewall.allowedTCPPorts = [ 80 443 8443 ];
+      networking.firewall.allowedUDPPorts = [ 443 8443 ];
+     };
+    client = { ... }: {
+      environment.systemPackages = [ pkgs.curlHTTP3 ];
     };
   };
   testScript = ''
+    # Helpers
+    def cmd(command):
+      print(f"+{command}")
+      r = os.system(command)
+      if r != 0:
+        raise Exception(f"Command {command} failed with exit code {r}")
+
+    def openssl(command):
+      cmd(f"${pkgs.openssl}/bin/openssl {command}")
+
+    # Generate CA.
+    openssl("req -new -newkey rsa:4096 -nodes -x509 -days 7 -subj '/C=ZZ/ST=Cloud/L=Unspecified/O=NixOS/OU=Tests/CN=CA Certificate' -keyout cacert.key -out cacert.crt")
+
+    # Generate and sign Server.
+    openssl("req -newkey rsa:4096 -nodes -subj '/CN=server/OU=Tests/O=NixOS' -keyout server.key -out server.csr")
+    openssl("x509 -req -in server.csr -out server.crt -CA cacert.crt -CAkey cacert.key -days 7")
+    cmd("cat server.crt server.key > fullchain.pem")
+
+    # Generate and sign Client.
+    openssl("req -newkey rsa:4096 -nodes -subj '/CN=client/OU=Tests/O=NixOS' -keyout client.key -out client.csr")
+    openssl("x509 -req -in client.csr -out client.crt -CA cacert.crt -CAkey cacert.key -days 7")
+    cmd("cat client.crt client.key > client.pem")
+
+    # Start the actual test.
     start_all()
-    machine.wait_for_unit("multi-user.target")
-    machine.wait_for_unit("haproxy.service")
-    machine.wait_for_unit("httpd.service")
-    assert "We are all good!" in machine.succeed("curl -fk http://localhost:80/index.txt")
-    assert "haproxy_process_pool_allocated_bytes" in machine.succeed(
-        "curl -fk http://localhost:80/metrics"
-    )
+    server.copy_from_host("fullchain.pem", "/etc/ssl/fullchain.pem")
+    server.copy_from_host("cacert.crt", "/etc/ssl/cacert.crt")
+    server.succeed("chmod 0644 /etc/ssl/fullchain.pem /etc/ssl/cacert.crt")
+
+    client.copy_from_host("cacert.crt", "/etc/ssl/cacert.crt")
+    client.copy_from_host("client.pem", "/root/client.pem")
+
+    server.wait_for_unit("multi-user.target")
+    server.wait_for_unit("haproxy.service")
+    server.wait_for_unit("httpd.service")
+
+    assert "We are all good!" in client.succeed("curl -f http://server/index.txt")
+    assert "haproxy_process_pool_allocated_bytes" in client.succeed("curl -f http://server/metrics")
+
+    with subtest("https"):
+      assert "We are all good!" in client.succeed("curl -f --cacert /etc/ssl/cacert.crt https://server/index.txt")
+
+    with subtest("https-cert-auth"):
+      # Client must succeed in authenticating with the right certificate.
+      assert "We are all good!" in client.succeed("curl -f --cacert /etc/ssl/cacert.crt --cert-type pem --cert /root/client.pem https://server:8443/index.txt")
+      # Client must fail without certificate.
+      client.fail("curl --cacert /etc/ssl/cacert.crt https://server:8443/index.txt")
+
+    with subtest("h3"):
+      assert "We are all good!" in client.succeed("curl -f --http3-only --cacert /etc/ssl/cacert.crt https://server/index.txt")
+
+    with subtest("h3-cert-auth"):
+      # Client must succeed in authenticating with the right certificate.
+      assert "We are all good!" in client.succeed("curl -f --http3-only --cacert /etc/ssl/cacert.crt --cert-type pem --cert /root/client.pem https://server:8443/index.txt")
+      # Client must fail without certificate.
+      client.fail("curl -f --http3-only --cacert /etc/ssl/cacert.crt https://server:8443/index.txt")
 
     with subtest("reload"):
-        machine.succeed("systemctl reload haproxy")
+        server.succeed("systemctl reload haproxy")
         # wait some time to ensure the following request hits the reloaded haproxy
-        machine.sleep(5)
-        assert "We are all good!" in machine.succeed(
-            "curl -fk http://localhost:80/index.txt"
-        )
+        server.sleep(5)
+        assert "We are all good!" in client.succeed("curl -f http://server/index.txt")
   '';
 })
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
index f54506224e51f..e38834961e13a 100644
--- a/nixos/tests/hardened.nix
+++ b/nixos/tests/hardened.nix
@@ -28,7 +28,7 @@ import ./make-test-python.nix ({ pkgs, ... } : {
         };
       };
       boot.extraModulePackages =
-        optional (versionOlder config.boot.kernelPackages.kernel.version "5.6")
+        pkgs.lib.optional (pkgs.lib.versionOlder config.boot.kernelPackages.kernel.version "5.6")
           config.boot.kernelPackages.wireguard;
       boot.kernelModules = [ "wireguard" ];
     };
diff --git a/nixos/tests/harmonia.nix b/nixos/tests/harmonia.nix
index 6cf9ad4d23358..a9beac82f8e12 100644
--- a/nixos/tests/harmonia.nix
+++ b/nixos/tests/harmonia.nix
@@ -13,6 +13,9 @@
 
       networking.firewall.allowedTCPPorts = [ 5000 ];
       system.extraDependencies = [ pkgs.emptyFile ];
+
+      # check that extra-allowed-users is effective for harmonia
+      nix.settings.allowed-users = [];
     };
 
     client01 = {
diff --git a/nixos/tests/hedgedoc.nix b/nixos/tests/hedgedoc.nix
index 410350d83627c..16e0dc14e947b 100644
--- a/nixos/tests/hedgedoc.nix
+++ b/nixos/tests/hedgedoc.nix
@@ -8,25 +8,54 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
   nodes = {
     hedgedocSqlite = { ... }: {
+      services.hedgedoc.enable = true;
+    };
+
+    hedgedocPostgresWithTCPSocket = { ... }: {
+      systemd.services.hedgedoc.after = [ "postgresql.service" ];
       services = {
         hedgedoc = {
           enable = true;
-          settings.dbURL = "sqlite:///var/lib/hedgedoc/hedgedoc.db";
+          settings.db = {
+            dialect = "postgres";
+            user = "hedgedoc";
+            password = "$DB_PASSWORD";
+            host = "localhost";
+            port = 5432;
+            database = "hedgedocdb";
+          };
+
+          /*
+           * Do not use pkgs.writeText for secrets as
+           * they will end up in the world-readable Nix store.
+           */
+          environmentFile = pkgs.writeText "hedgedoc-env" ''
+            DB_PASSWORD=snakeoilpassword
+          '';
+        };
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script.sql" ''
+            CREATE ROLE hedgedoc LOGIN PASSWORD 'snakeoilpassword';
+            CREATE DATABASE hedgedocdb OWNER hedgedoc;
+          '';
         };
       };
     };
 
-    hedgedocPostgres = { ... }: {
+    hedgedocPostgresWithUNIXSocket = { ... }: {
       systemd.services.hedgedoc.after = [ "postgresql.service" ];
       services = {
         hedgedoc = {
           enable = true;
-          settings.dbURL = "postgres://hedgedoc:\${DB_PASSWORD}@localhost:5432/hedgedocdb";
+          settings.db = {
+            dialect = "postgres";
+            user = "hedgedoc";
+            password = "$DB_PASSWORD";
+            host = "/run/postgresql";
+            database = "hedgedocdb";
+          };
 
-          /*
-           * Do not use pkgs.writeText for secrets as
-           * they will end up in the world-readable Nix store.
-           */
           environmentFile = pkgs.writeText "hedgedoc-env" ''
             DB_PASSWORD=snakeoilpassword
           '';
@@ -50,11 +79,18 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
         hedgedocSqlite.wait_for_open_port(3000)
         hedgedocSqlite.wait_until_succeeds("curl -sSf http://localhost:3000/new")
 
-    with subtest("HedgeDoc postgres"):
-        hedgedocPostgres.wait_for_unit("postgresql.service")
-        hedgedocPostgres.wait_for_unit("hedgedoc.service")
-        hedgedocPostgres.wait_for_open_port(5432)
-        hedgedocPostgres.wait_for_open_port(3000)
-        hedgedocPostgres.wait_until_succeeds("curl -sSf http://localhost:3000/new")
+    with subtest("HedgeDoc postgres with TCP socket"):
+        hedgedocPostgresWithTCPSocket.wait_for_unit("postgresql.service")
+        hedgedocPostgresWithTCPSocket.wait_for_unit("hedgedoc.service")
+        hedgedocPostgresWithTCPSocket.wait_for_open_port(5432)
+        hedgedocPostgresWithTCPSocket.wait_for_open_port(3000)
+        hedgedocPostgresWithTCPSocket.wait_until_succeeds("curl -sSf http://localhost:3000/new")
+
+    with subtest("HedgeDoc postgres with UNIX socket"):
+        hedgedocPostgresWithUNIXSocket.wait_for_unit("postgresql.service")
+        hedgedocPostgresWithUNIXSocket.wait_for_unit("hedgedoc.service")
+        hedgedocPostgresWithUNIXSocket.wait_for_open_port(5432)
+        hedgedocPostgresWithUNIXSocket.wait_for_open_port(3000)
+        hedgedocPostgresWithUNIXSocket.wait_until_succeeds("curl -sSf http://localhost:3000/new")
   '';
 })
diff --git a/nixos/tests/hockeypuck.nix b/nixos/tests/hockeypuck.nix
index 2b9dba8720aba..675d6b226ad20 100644
--- a/nixos/tests/hockeypuck.nix
+++ b/nixos/tests/hockeypuck.nix
@@ -35,7 +35,7 @@ in {
       ensureDatabases = [ "hockeypuck" ];
       ensureUsers = [{
         name = "hockeypuck";
-        ensurePermissions."DATABASE hockeypuck" = "ALL PRIVILEGES";
+        ensureDBOwnership = true;
       }];
     };
   };
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index e06c52a5f41c7..05fb2fa1e06aa 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -12,9 +12,7 @@ in {
       ensureDatabases = [ "hass" ];
       ensureUsers = [{
         name = "hass";
-        ensurePermissions = {
-          "DATABASE hass" = "ALL PRIVILEGES";
-        };
+        ensureDBOwnership = true;
       }];
     };
 
@@ -43,6 +41,16 @@ in {
         psycopg2
       ];
 
+      # test loading custom components
+      customComponents = with pkgs.home-assistant-custom-components; [
+        prometheus_sensor
+      ];
+
+      # test loading lovelace modules
+      customLovelaceModules = with pkgs.home-assistant-custom-lovelace-modules; [
+        mini-graph-card
+      ];
+
       config = {
         homeassistant = {
           name = "Home";
@@ -114,6 +122,14 @@ in {
       inheritParentConfig = true;
       configuration.services.home-assistant.config.backup = {};
     };
+
+    specialisation.removeCustomThings = {
+      inheritParentConfig = true;
+      configuration.services.home-assistant = {
+        customComponents = lib.mkForce [];
+        customLovelaceModules = lib.mkForce [];
+      };
+    };
   };
 
   testScript = { nodes, ... }: let
@@ -161,6 +177,14 @@ in {
         hass.wait_for_open_port(8123)
         hass.succeed("curl --fail http://localhost:8123/lovelace")
 
+    with subtest("Check that custom components get installed"):
+        hass.succeed("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
+        hass.wait_until_succeeds("journalctl -u home-assistant.service | grep -q 'We found a custom integration prometheus_sensor which has not been tested by Home Assistant'")
+
+    with subtest("Check that lovelace modules are referenced and fetchable"):
+        hass.succeed("grep -q 'mini-graph-card-bundle.js' '${configDir}/configuration.yaml'")
+        hass.succeed("curl --fail http://localhost:8123/local/nixos-lovelace-modules/mini-graph-card-bundle.js")
+
     with subtest("Check that optional dependencies are in the PYTHONPATH"):
         env = get_unit_property("Environment")
         python_path = env.split("PYTHONPATH=")[1].split()[0]
@@ -200,6 +224,13 @@ in {
         for domain in ["backup"]:
             assert f"Setup of domain {domain} took" in journal, f"{domain} setup missing"
 
+    with subtest("Check custom components and custom lovelace modules get removed"):
+        cursor = get_journal_cursor()
+        hass.succeed("${system}/specialisation/removeCustomThings/bin/switch-to-configuration test")
+        hass.fail("grep -q 'mini-graph-card-bundle.js' '${configDir}/ui-lovelace.yaml'")
+        hass.fail("test -f ${configDir}/custom_components/prometheus_sensor/manifest.json")
+        wait_for_homeassistant(cursor)
+
     with subtest("Check that no errors were logged"):
         hass.fail("journalctl -u home-assistant -o cat | grep -q ERROR")
 
diff --git a/nixos/tests/hostname.nix b/nixos/tests/hostname.nix
index 6122e2ffeb83a..dffec956bc0b6 100644
--- a/nixos/tests/hostname.nix
+++ b/nixos/tests/hostname.nix
@@ -34,6 +34,7 @@ let
 
         machine = ${hostName}
 
+        machine.systemctl("start network-online.target")
         machine.wait_for_unit("network-online.target")
 
         # Test if NixOS computes the correct FQDN (either a FQDN or an error/null):
diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix
index baf18afbc5690..98c3c6fbae9fc 100644
--- a/nixos/tests/hydra/default.nix
+++ b/nixos/tests/hydra/default.nix
@@ -17,7 +17,7 @@ let
   makeHydraTest = with pkgs.lib; name: package: makeTest {
     name = "hydra-${name}";
     meta = with pkgs.lib.maintainers; {
-      maintainers = [ lewo ma27 ];
+      maintainers = [ lewo ];
     };
 
     nodes.machine = { pkgs, lib, ... }: {
diff --git a/nixos/tests/incron.nix b/nixos/tests/incron.nix
index c978ff27dfad5..d016360ba0ef8 100644
--- a/nixos/tests/incron.nix
+++ b/nixos/tests/incron.nix
@@ -13,9 +13,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       '';
 
       # ensure the directory to be monitored exists before incron is started
-      system.activationScripts.incronTest = ''
-        mkdir /test
-      '';
+      systemd.tmpfiles.settings.incron-test = {
+        "/test".d = { };
+      };
     };
 
   testScript = ''
diff --git a/nixos/tests/incus/container.nix b/nixos/tests/incus/container.nix
new file mode 100644
index 0000000000000..2fa1709c7484b
--- /dev/null
+++ b/nixos/tests/incus/container.nix
@@ -0,0 +1,111 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+let
+  releases = import ../../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+    };
+  };
+
+  container-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+  container-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+in
+{
+  name = "incus-container";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { ... }: {
+    virtualisation = {
+      # Ensure test VM has enough resources for creating and managing guests
+      cores = 2;
+      memorySize = 1024;
+      diskSize = 4096;
+
+      incus.enable = true;
+    };
+  };
+
+  testScript = ''
+    def instance_is_up(_) -> bool:
+        status, _ = machine.execute("incus exec container --disable-stdin --force-interactive /run/current-system/sw/bin/true")
+        return status == 0
+
+    def set_container(config):
+        machine.succeed(f"incus config set container {config}")
+        machine.succeed("incus restart container")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+    machine.wait_for_unit("incus.service")
+
+    # no preseed should mean no service
+    machine.fail("systemctl status incus-preseed.service")
+
+    machine.succeed("incus admin init --minimal")
+
+    with subtest("Container image can be imported"):
+        machine.succeed("incus image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs}/*/*.tar.xz --alias nixos")
+
+    with subtest("Container can be launched and managed"):
+        machine.succeed("incus launch nixos container")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+        machine.succeed("echo true | incus exec container /run/current-system/sw/bin/bash -")
+
+    with subtest("Container mounts lxcfs overlays"):
+        machine.succeed("incus exec container mount | grep 'lxcfs on /proc/cpuinfo type fuse.lxcfs'")
+        machine.succeed("incus exec container mount | grep 'lxcfs on /proc/meminfo type fuse.lxcfs'")
+
+    with subtest("Container CPU limits can be managed"):
+        set_container("limits.cpu 1")
+        cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
+        assert cpuinfo == "1", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 1, got: {cpuinfo}"
+
+        set_container("limits.cpu 2")
+        cpuinfo = machine.succeed("incus exec container grep -- -c ^processor /proc/cpuinfo").strip()
+        assert cpuinfo == "2", f"Wrong number of CPUs reported from /proc/cpuinfo, want: 2, got: {cpuinfo}"
+
+    with subtest("Container memory limits can be managed"):
+        set_container("limits.memory 64MB")
+        meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
+        meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
+        assert meminfo_bytes == "62500 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '62500 kB', got: '{meminfo_bytes}'"
+
+        set_container("limits.memory 128MB")
+        meminfo = machine.succeed("incus exec container grep -- MemTotal /proc/meminfo").strip()
+        meminfo_bytes = " ".join(meminfo.split(' ')[-2:])
+        assert meminfo_bytes == "125000 kB", f"Wrong amount of memory reported from /proc/meminfo, want: '125000 kB', got: '{meminfo_bytes}'"
+
+    with subtest("lxc-container generator configures plain container"):
+        machine.execute("incus delete --force container")
+        machine.succeed("incus launch nixos container")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+        machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+
+    with subtest("lxc-container generator configures nested container"):
+        machine.execute("incus delete --force container")
+        machine.succeed("incus launch nixos container --config security.nesting=true")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+        machine.fail("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+        target = machine.succeed("incus exec container readlink -- -f /run/systemd/system/systemd-binfmt.service").strip()
+        assert target == "/dev/null", "lxc generator did not correctly mask /run/systemd/system/systemd-binfmt.service"
+
+    with subtest("lxc-container generator configures privileged container"):
+        machine.execute("incus delete --force container")
+        machine.succeed("incus launch nixos container --config security.privileged=true")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+        # give generator an extra second to run
+        machine.sleep(1)
+
+        machine.succeed("incus exec container test -- -e /run/systemd/system/service.d/zzz-lxc-service.conf")
+  '';
+})
diff --git a/nixos/tests/incus/default.nix b/nixos/tests/incus/default.nix
new file mode 100644
index 0000000000000..26e8a4ac4c772
--- /dev/null
+++ b/nixos/tests/incus/default.nix
@@ -0,0 +1,13 @@
+{
+  system ? builtins.currentSystem,
+  config ? { },
+  pkgs ? import ../../.. { inherit system config; },
+  handleTestOn,
+}:
+{
+  container = import ./container.nix { inherit system pkgs; };
+  lxd-to-incus = import ./lxd-to-incus.nix { inherit system pkgs; };
+  preseed = import ./preseed.nix { inherit system pkgs; };
+  socket-activated = import ./socket-activated.nix { inherit system pkgs; };
+  virtual-machine = handleTestOn [ "x86_64-linux" ] ./virtual-machine.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/incus/lxd-to-incus.nix b/nixos/tests/incus/lxd-to-incus.nix
new file mode 100644
index 0000000000000..67245b54e7527
--- /dev/null
+++ b/nixos/tests/incus/lxd-to-incus.nix
@@ -0,0 +1,112 @@
+import ../make-test-python.nix (
+
+  { pkgs, lib, ... }:
+
+  let
+    releases = import ../../release.nix { configuration.documentation.enable = lib.mkForce false; };
+
+    container-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+    container-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+  in
+  {
+    name = "lxd-to-incus";
+
+    meta = {
+      maintainers = lib.teams.lxc.members;
+    };
+
+    nodes.machine =
+      { lib, ... }:
+      {
+        environment.systemPackages = [ pkgs.lxd-to-incus ];
+
+        virtualisation = {
+          diskSize = 6144;
+          cores = 2;
+          memorySize = 2048;
+
+          lxd.enable = true;
+          lxd.preseed = {
+            networks = [
+              {
+                name = "nixostestbr0";
+                type = "bridge";
+                config = {
+                  "ipv4.address" = "10.0.100.1/24";
+                  "ipv4.nat" = "true";
+                };
+              }
+            ];
+            profiles = [
+              {
+                name = "default";
+                devices = {
+                  eth0 = {
+                    name = "eth0";
+                    network = "nixostestbr0";
+                    type = "nic";
+                  };
+                  root = {
+                    path = "/";
+                    pool = "nixostest_pool";
+                    size = "35GiB";
+                    type = "disk";
+                  };
+                };
+              }
+              {
+                name = "nixos_notdefault";
+                devices = { };
+              }
+            ];
+            storage_pools = [
+              {
+                name = "nixostest_pool";
+                driver = "dir";
+              }
+            ];
+          };
+
+          incus.enable = true;
+        };
+      };
+
+    testScript = ''
+      def lxd_wait_for_preseed(_) -> bool:
+        _, output = machine.systemctl("is-active lxd-preseed.service")
+        return ("inactive" in output)
+
+      def lxd_instance_is_up(_) -> bool:
+          status, _ = machine.execute("lxc exec container --disable-stdin --force-interactive /run/current-system/sw/bin/true")
+          return status == 0
+
+      def incus_instance_is_up(_) -> bool:
+          status, _ = machine.execute("incus exec container --disable-stdin --force-interactive /run/current-system/sw/bin/true")
+          return status == 0
+
+      with machine.nested("initialize lxd and resources"):
+        machine.wait_for_unit("sockets.target")
+        machine.wait_for_unit("lxd.service")
+        retry(lxd_wait_for_preseed)
+
+        machine.succeed("lxc image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs}/*/*.tar.xz --alias nixos")
+        machine.succeed("lxc launch nixos container")
+        retry(lxd_instance_is_up)
+
+      machine.wait_for_unit("incus.service")
+
+      with machine.nested("run migration"):
+          machine.succeed("lxd-to-incus --yes")
+
+      with machine.nested("verify resources migrated to incus"):
+          machine.succeed("incus config show container")
+          retry(incus_instance_is_up)
+          machine.succeed("incus exec container -- true")
+          machine.succeed("incus profile show default | grep nixostestbr0")
+          machine.succeed("incus profile show default | grep nixostest_pool")
+          machine.succeed("incus profile show nixos_notdefault")
+          machine.succeed("incus storage show nixostest_pool")
+          machine.succeed("incus network show nixostestbr0")
+    '';
+  }
+)
diff --git a/nixos/tests/incus/preseed.nix b/nixos/tests/incus/preseed.nix
new file mode 100644
index 0000000000000..a488d71f3c92a
--- /dev/null
+++ b/nixos/tests/incus/preseed.nix
@@ -0,0 +1,62 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+{
+  name = "incus-preseed";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      incus.enable = true;
+
+      incus.preseed = {
+        networks = [
+          {
+            name = "nixostestbr0";
+            type = "bridge";
+            config = {
+              "ipv4.address" = "10.0.100.1/24";
+              "ipv4.nat" = "true";
+            };
+          }
+        ];
+        profiles = [
+          {
+            name = "nixostest_default";
+            devices = {
+              eth0 = {
+                name = "eth0";
+                network = "nixostestbr0";
+                type = "nic";
+              };
+              root = {
+                path = "/";
+                pool = "default";
+                size = "35GiB";
+                type = "disk";
+              };
+            };
+          }
+        ];
+        storage_pools = [
+          {
+            name = "nixostest_pool";
+            driver = "dir";
+          }
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("incus.service")
+    machine.wait_for_unit("incus-preseed.service")
+
+    with subtest("Verify preseed resources created"):
+      machine.succeed("incus profile show nixostest_default")
+      machine.succeed("incus network info nixostestbr0")
+      machine.succeed("incus storage show nixostest_pool")
+  '';
+})
diff --git a/nixos/tests/incus/socket-activated.nix b/nixos/tests/incus/socket-activated.nix
new file mode 100644
index 0000000000000..fca536b7054f0
--- /dev/null
+++ b/nixos/tests/incus/socket-activated.nix
@@ -0,0 +1,28 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+{
+  name = "incus-socket-activated";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      incus.enable = true;
+      incus.socketActivation = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("incus.socket")
+
+    # ensure service is not running by default
+    machine.fail("systemctl is-active incus.service")
+    machine.fail("systemctl is-active incus-preseed.service")
+
+    # access the socket and ensure the service starts
+    machine.succeed("incus list")
+    machine.wait_for_unit("incus.service")
+  '';
+})
diff --git a/nixos/tests/incus/virtual-machine.nix b/nixos/tests/incus/virtual-machine.nix
new file mode 100644
index 0000000000000..343a25ca72970
--- /dev/null
+++ b/nixos/tests/incus/virtual-machine.nix
@@ -0,0 +1,60 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  releases = import ../../release.nix {
+    configuration = {
+      # Building documentation makes the test unnecessarily take a longer time:
+      documentation.enable = lib.mkForce false;
+
+      # Our tests require `grep` & friends:
+      environment.systemPackages = with pkgs; [busybox];
+    };
+  };
+
+  vm-image-metadata = releases.lxdVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system};
+  vm-image-disk = releases.lxdVirtualMachineImage.${pkgs.stdenv.hostPlatform.system};
+
+  instance-name = "instance1";
+in
+{
+  name = "incus-virtual-machine";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = {...}: {
+    virtualisation = {
+      # Ensure test VM has enough resources for creating and managing guests
+      cores = 2;
+      memorySize = 1024;
+      diskSize = 4096;
+
+      incus.enable = true;
+    };
+  };
+
+  testScript = ''
+    def instance_is_up(_) -> bool:
+      status, _ = machine.execute("incus exec ${instance-name} --disable-stdin --force-interactive /run/current-system/sw/bin/true")
+      return status == 0
+
+    machine.wait_for_unit("incus.service")
+
+    machine.succeed("incus admin init --minimal")
+
+    with subtest("virtual-machine image can be imported"):
+        machine.succeed("incus image import ${vm-image-metadata}/*/*.tar.xz ${vm-image-disk}/nixos.qcow2 --alias nixos")
+
+    with subtest("virtual-machine can be launched and become available"):
+        machine.succeed("incus launch nixos ${instance-name} --vm --config limits.memory=512MB --config security.secureboot=false")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+
+    with subtest("lxd-agent is started"):
+        machine.succeed("incus exec ${instance-name} systemctl is-active lxd-agent")
+
+    with subtest("lxd-agent has a valid path"):
+        machine.succeed("incus exec ${instance-name} -- bash -c 'true'")
+  '';
+})
diff --git a/nixos/tests/initrd-network-openvpn/default.nix b/nixos/tests/initrd-network-openvpn/default.nix
index 769049905eb8c..69db7dd1037f7 100644
--- a/nixos/tests/initrd-network-openvpn/default.nix
+++ b/nixos/tests/initrd-network-openvpn/default.nix
@@ -59,18 +59,19 @@ import ../make-test-python.nix ({ lib, ...}:
 
             # This command does not fork to keep the VM in the state where
             # only the initramfs is loaded
-            preLVMCommands =
-            ''
-              /bin/nc -p 1234 -lke /bin/echo TESTVALUE
-            '';
+            preLVMCommands = lib.mkIf (!systemdStage1)
+              ''
+                /bin/nc -p 1234 -lke /bin/echo TESTVALUE
+              '';
 
             network = {
               enable = true;
 
               # Work around udhcpc only getting a lease on eth0
-              postCommands = ''
-                /bin/ip addr add 192.168.1.2/24 dev eth1
-              '';
+              postCommands = lib.mkIf (!systemdStage1)
+                ''
+                  /bin/ip addr add 192.168.1.2/24 dev eth1
+                '';
 
               # Example configuration for OpenVPN
               # This is the main reason for this test
diff --git a/nixos/tests/input-remapper.nix b/nixos/tests/input-remapper.nix
index 1b0350063f7f2..2ef55a01b2905 100644
--- a/nixos/tests/input-remapper.nix
+++ b/nixos/tests/input-remapper.nix
@@ -46,7 +46,8 @@ import ./make-test-python.nix ({ pkgs, ... }:
       machine.execute("su - sybil -c input-remapper-gtk >&2 &")
 
       machine.wait_for_text("Input Remapper")
-      machine.wait_for_text("Preset")
-      machine.wait_for_text("Change Key")
+      machine.wait_for_text("Device")
+      machine.wait_for_text("Presets")
+      machine.wait_for_text("Editor")
     '';
   })
diff --git a/nixos/tests/installed-tests/flatpak.nix b/nixos/tests/installed-tests/flatpak.nix
index 9524d890c4025..fa191202f52d4 100644
--- a/nixos/tests/installed-tests/flatpak.nix
+++ b/nixos/tests/installed-tests/flatpak.nix
@@ -7,6 +7,7 @@ makeInstalledTest {
   testConfig = {
     xdg.portal.enable = true;
     xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+    xdg.portal.config.common.default = "gtk";
     services.flatpak.enable = true;
     environment.systemPackages = with pkgs; [ gnupg ostree python3 ];
     virtualisation.memorySize = 2047;
diff --git a/nixos/tests/installer-systemd-stage-1.nix b/nixos/tests/installer-systemd-stage-1.nix
index 85155a6c682b3..662017935412c 100644
--- a/nixos/tests/installer-systemd-stage-1.nix
+++ b/nixos/tests/installer-systemd-stage-1.nix
@@ -8,18 +8,21 @@
   # them when fixed.
   inherit (import ./installer.nix { inherit system config pkgs; systemdStage1 = true; })
     # bcache
+    bcachefsSimple
+    bcachefsEncrypted
     btrfsSimple
     btrfsSubvolDefault
     btrfsSubvolEscape
     btrfsSubvols
-    # encryptedFSWithKeyfile
+    encryptedFSWithKeyfile
     # grub1
-    # luksroot
-    # luksroot-format1
-    # luksroot-format2
+    luksroot
+    luksroot-format1
+    luksroot-format2
     # lvm
     separateBoot
     separateBootFat
+    separateBootZfs
     simple
     simpleLabels
     simpleProvided
@@ -30,6 +33,10 @@
     stratisRoot
     swraid
     zfsroot
+    clevisLuks
+    clevisLuksFallback
+    clevisZfs
+    clevisZfsFallback
     ;
 
 }
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 56ba85b76e6f5..7576fae41f83b 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -12,6 +12,7 @@ let
   # The configuration to install.
   makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi
                , extraConfig, forceGrubReinstallCount ? 0, flake ? false
+               , clevisTest
                }:
     pkgs.writeText "configuration.nix" ''
       { config, lib, pkgs, modulesPath, ... }:
@@ -52,6 +53,15 @@ let
 
         boot.initrd.secrets."/etc/secret" = ./secret;
 
+        ${optionalString clevisTest ''
+          boot.kernelParams = [ "console=tty0" "ip=192.168.1.1:::255.255.255.0::eth1:none" ];
+          boot.initrd = {
+            availableKernelModules = [ "tpm_tis" ];
+            clevis = { enable = true; useTang = true; };
+            network.enable = true;
+          };
+          ''}
+
         users.users.alice = {
           isNormalUser = true;
           home = "/home/alice";
@@ -69,9 +79,9 @@ let
   # disk, and then reboot from the hard disk.  It's parameterized with
   # a test script fragment `createPartitions', which must create
   # partitions and filesystems.
-  testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi
-                  , grubIdentifier, preBootCommands, postBootCommands, extraConfig
-                  , testSpecialisationConfig, testFlakeSwitch
+  testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi, grubIdentifier
+                  , postInstallCommands, preBootCommands, postBootCommands, extraConfig
+                  , testSpecialisationConfig, testFlakeSwitch, clevisTest, clevisFallbackTest
                   }:
     let iface = "virtio";
         isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
@@ -79,12 +89,16 @@ let
     in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
       machine.succeed("true")
     '' else ''
+      import subprocess
+      tpm_folder = os.environ['NIX_BUILD_TOP']
       def assemble_qemu_flags():
           flags = "-cpu max"
           ${if (system == "x86_64-linux" || system == "i686-linux")
             then ''flags += " -m 1024"''
             else ''flags += " -m 768 -enable-kvm -machine virt,gic-version=host"''
           }
+          ${optionalString clevisTest ''flags += f" -chardev socket,id=chrtpm,path={tpm_folder}/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"''}
+          ${optionalString clevisTest ''flags += " -device virtio-net-pci,netdev=vlan1,mac=52:54:00:12:11:02 -netdev vde,id=vlan1,sock=\"$QEMU_VDE_SOCKET_1\""''}
           return flags
 
 
@@ -110,8 +124,47 @@ let
       def create_machine_named(name):
           return create_machine({**default_flags, "name": name})
 
+      class Tpm:
+            def __init__(self):
+                self.start()
+
+            def start(self):
+                self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm",
+                    "socket",
+                    "--tpmstate", f"dir={tpm_folder}/swtpm",
+                    "--ctrl", f"type=unixio,path={tpm_folder}/swtpm-sock",
+                    "--tpm2"
+                    ])
+
+                # Check whether starting swtpm failed
+                try:
+                    exit_code = self.proc.wait(timeout=0.2)
+                    if exit_code is not None and exit_code != 0:
+                        raise Exception("failed to start swtpm")
+                except subprocess.TimeoutExpired:
+                    pass
+
+            """Check whether the swtpm process exited due to an error"""
+            def check(self):
+                exit_code = self.proc.poll()
+                if exit_code is not None and exit_code != 0:
+                    raise Exception("swtpm process died")
+
+
+      os.mkdir(f"{tpm_folder}/swtpm")
+      tpm = Tpm()
+      tpm.check()
+
+      start_all()
+      ${optionalString clevisTest ''
+      tang.wait_for_unit("sockets.target")
+      tang.systemctl("start network-online.target")
+      tang.wait_for_unit("network-online.target")
+      machine.systemctl("start network-online.target")
+      machine.wait_for_unit("network-online.target")
+      ''}
+      machine.wait_for_unit("multi-user.target")
 
-      machine.start()
 
       with subtest("Assert readiness of login prompt"):
           machine.succeed("echo hello")
@@ -127,13 +180,24 @@ let
           machine.copy_from_host(
               "${ makeConfig {
                     inherit bootLoader grubDevice grubIdentifier
-                            grubUseEfi extraConfig;
+                            grubUseEfi extraConfig clevisTest;
                   }
               }",
               "/mnt/etc/nixos/configuration.nix",
           )
           machine.copy_from_host("${pkgs.writeText "secret" "secret"}", "/mnt/etc/nixos/secret")
 
+      ${optionalString clevisTest ''
+        with subtest("Create the Clevis secret with Tang"):
+             machine.systemctl("start network-online.target")
+             machine.wait_for_unit("network-online.target")
+             machine.succeed('echo -n password | clevis encrypt sss \'{"t": 2, "pins": {"tpm2": {}, "tang": {"url": "http://192.168.1.2"}}}\' -y > /mnt/etc/nixos/clevis-secret.jwe')''}
+
+      ${optionalString clevisFallbackTest ''
+        with subtest("Shutdown Tang to check fallback to interactive prompt"):
+            tang.shutdown()
+      ''}
+
       with subtest("Perform the installation"):
           machine.succeed("nixos-install < /dev/null >&2")
 
@@ -153,6 +217,8 @@ let
               """
           )
 
+      ${postInstallCommands}
+
       with subtest("Shutdown system after installation"):
           machine.succeed("umount -R /mnt")
           machine.succeed("sync")
@@ -198,7 +264,7 @@ let
           machine.copy_from_host_via_shell(
               "${ makeConfig {
                     inherit bootLoader grubDevice grubIdentifier
-                            grubUseEfi extraConfig;
+                            grubUseEfi extraConfig clevisTest;
                     forceGrubReinstallCount = 1;
                   }
               }",
@@ -227,7 +293,7 @@ let
       machine.copy_from_host_via_shell(
           "${ makeConfig {
                 inherit bootLoader grubDevice grubIdentifier
-                grubUseEfi extraConfig;
+                grubUseEfi extraConfig clevisTest;
                 forceGrubReinstallCount = 2;
               }
           }",
@@ -301,7 +367,7 @@ let
         """)
         machine.copy_from_host_via_shell(
           "${makeConfig {
-               inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig;
+               inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
                forceGrubReinstallCount = 1;
                flake = true;
             }}",
@@ -368,13 +434,17 @@ let
 
 
   makeInstallerTest = name:
-    { createPartitions, preBootCommands ? "", postBootCommands ? "", extraConfig ? ""
+    { createPartitions
+    , postInstallCommands ? "", preBootCommands ? "", postBootCommands ? ""
+    , extraConfig ? ""
     , extraInstallerConfig ? {}
     , bootLoader ? "grub" # either "grub" or "systemd-boot"
     , grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
     , enableOCR ? false, meta ? {}
     , testSpecialisationConfig ? false
     , testFlakeSwitch ? false
+    , clevisTest ? false
+    , clevisFallbackTest ? false
     }:
     makeTest {
       inherit enableOCR;
@@ -412,13 +482,13 @@ let
           virtualisation.rootDevice = "/dev/vdb";
           virtualisation.bootLoaderDevice = "/dev/vda";
           virtualisation.qemu.diskInterface = "virtio";
-
-          # We don't want to have any networking in the guest whatsoever.
-          # Also, if any vlans are enabled, the guest will reboot
-          # (with a different configuration for legacy reasons),
-          # and spend 5 minutes waiting for the vlan interface to show up
-          # (which will never happen).
-          virtualisation.vlans = [];
+          virtualisation.qemu.options = mkIf (clevisTest) [
+            "-chardev socket,id=chrtpm,path=$NIX_BUILD_TOP/swtpm-sock"
+            "-tpmdev emulator,id=tpm0,chardev=chrtpm"
+            "-device tpm-tis,tpmdev=tpm0"
+          ];
+          # We don't want to have any networking in the guest apart from the clevis tests.
+          virtualisation.vlans = mkIf (!clevisTest) [];
 
           boot.loader.systemd-boot.enable = mkIf (bootLoader == "systemd-boot") true;
 
@@ -443,14 +513,8 @@ let
             ntp
             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 ]))
+            (python3.withPackages (p: [ p.mistune ]))
             shared-mime-info
             sudo
             texinfo
@@ -467,7 +531,7 @@ let
           in [
             (pkgs.grub2.override { inherit zfsSupport; })
             (pkgs.grub2_efi.override { inherit zfsSupport; })
-          ]);
+          ]) ++ optionals clevisTest [ pkgs.klibc ];
 
           nix.settings = {
             substituters = mkForce [];
@@ -476,12 +540,21 @@ let
           };
         };
 
+      } // optionalAttrs clevisTest {
+        tang = {
+          services.tang = {
+            enable = true;
+            listenStream = [ "80" ];
+            ipAddressAllow = [ "192.168.1.0/24" ];
+          };
+          networking.firewall.allowedTCPPorts = [ 80 ];
+        };
       };
 
       testScript = testScriptFun {
-        inherit bootLoader createPartitions preBootCommands postBootCommands
+        inherit bootLoader createPartitions postInstallCommands preBootCommands postBootCommands
                 grubDevice grubIdentifier grubUseEfi extraConfig
-                testSpecialisationConfig testFlakeSwitch;
+                testSpecialisationConfig testFlakeSwitch clevisTest clevisFallbackTest;
       };
     };
 
@@ -511,7 +584,7 @@ let
       enableOCR = true;
       preBootCommands = ''
         machine.start()
-        machine.wait_for_text("Passphrase for")
+        machine.wait_for_text("[Pp]assphrase for")
         machine.send_chars("supersecret\n")
       '';
     };
@@ -582,6 +655,145 @@ let
       zfs = super.zfs.overrideAttrs(_: {meta.platforms = [];});}
     )];
   };
+
+ mkClevisBcachefsTest = { fallback ? false }: makeInstallerTest "clevis-bcachefs${optionalString fallback "-fallback"}" {
+    clevisTest = true;
+    clevisFallbackTest = fallback;
+    enableOCR = fallback;
+    extraInstallerConfig = {
+      imports = [ no-zfs-module ];
+      boot.supportedFilesystems = [ "bcachefs" ];
+      environment.systemPackages = with pkgs; [ keyutils clevis ];
+    };
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"
+        + " mkpart primary linux-swap 100M 1024M"
+        + " mkpart primary 1024M -1s",
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "keyctl link @u @s",
+        "echo -n password | mkfs.bcachefs -L root --encrypted /dev/vda3",
+        "echo -n password | bcachefs unlock /dev/vda3",
+        "echo -n password | mount -t bcachefs /dev/vda3 /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount LABEL=boot /mnt/boot",
+        "udevadm settle")
+    '';
+    extraConfig = ''
+      boot.initrd.clevis.devices."/dev/vda3".secretFile = "/etc/nixos/clevis-secret.jwe";
+
+      # We override what nixos-generate-config has generated because we do
+      # not know the UUID in advance.
+      fileSystems."/" = lib.mkForce { device = "/dev/vda3"; fsType = "bcachefs"; };
+    '';
+    preBootCommands = ''
+      tpm = Tpm()
+      tpm.check()
+    '' + optionalString fallback ''
+      machine.start()
+      machine.wait_for_text("enter passphrase for")
+      machine.send_chars("password\n")
+    '';
+  };
+
+  mkClevisLuksTest = { fallback ? false }: makeInstallerTest "clevis-luks${optionalString fallback "-fallback"}" {
+    clevisTest = true;
+    clevisFallbackTest = fallback;
+    enableOCR = fallback;
+    extraInstallerConfig = {
+      environment.systemPackages = with pkgs; [ clevis ];
+    };
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"
+        + " mkpart primary linux-swap 100M 1024M"
+        + " mkpart primary 1024M -1s",
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "modprobe dm_mod dm_crypt",
+        "echo -n password | cryptsetup luksFormat -q /dev/vda3 -",
+        "echo -n password | cryptsetup luksOpen --key-file - /dev/vda3 crypt-root",
+        "mkfs.ext3 -L nixos /dev/mapper/crypt-root",
+        "mount LABEL=nixos /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount LABEL=boot /mnt/boot",
+        "udevadm settle")
+    '';
+    extraConfig = ''
+      boot.initrd.clevis.devices."crypt-root".secretFile = "/etc/nixos/clevis-secret.jwe";
+    '';
+    preBootCommands = ''
+      tpm = Tpm()
+      tpm.check()
+    '' + optionalString fallback ''
+      machine.start()
+      ${if systemdStage1 then ''
+      machine.wait_for_text("Please enter")
+      '' else ''
+      machine.wait_for_text("Passphrase for")
+      ''}
+      machine.send_chars("password\n")
+    '';
+  };
+
+  mkClevisZfsTest = { fallback ? false }: makeInstallerTest "clevis-zfs${optionalString fallback "-fallback"}" {
+    clevisTest = true;
+    clevisFallbackTest = fallback;
+    enableOCR = fallback;
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "zfs" ];
+      environment.systemPackages = with pkgs; [ clevis ];
+    };
+    createPartitions = ''
+      machine.succeed(
+        "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+        + " mkpart primary ext2 1M 100MB"
+        + " mkpart primary linux-swap 100M 1024M"
+        + " mkpart primary 1024M -1s",
+        "udevadm settle",
+        "mkswap /dev/vda2 -L swap",
+        "swapon -L swap",
+        "zpool create -O mountpoint=legacy rpool /dev/vda3",
+        "echo -n password | zfs create"
+        + " -o encryption=aes-256-gcm -o keyformat=passphrase rpool/root",
+        "mount -t zfs rpool/root /mnt",
+        "mkfs.ext3 -L boot /dev/vda1",
+        "mkdir -p /mnt/boot",
+        "mount LABEL=boot /mnt/boot",
+        "udevadm settle")
+    '';
+    extraConfig = ''
+      boot.initrd.clevis.devices."rpool/root".secretFile = "/etc/nixos/clevis-secret.jwe";
+      boot.zfs.requestEncryptionCredentials = true;
+
+
+      # Using by-uuid overrides the default of by-id, and is unique
+      # to the qemu disks, as they don't produce by-id paths for
+      # some reason.
+      boot.zfs.devNodes = "/dev/disk/by-uuid/";
+      networking.hostId = "00000000";
+    '';
+    preBootCommands = ''
+      tpm = Tpm()
+      tpm.check()
+    '' + optionalString fallback ''
+      machine.start()
+      ${if systemdStage1 then ''
+      machine.wait_for_text("Enter key for rpool/root")
+      '' else ''
+      machine.wait_for_text("Key load error")
+      ''}
+      machine.send_chars("password\n")
+    '';
+  };
+
 in {
 
   # !!! `parted mkpart' seems to silently create overlapping partitions.
@@ -663,6 +875,78 @@ in {
     '';
   };
 
+  # Same as the previous, but with ZFS /boot.
+  separateBootZfs = makeInstallerTest "separateBootZfs" {
+    extraInstallerConfig = {
+      boot.supportedFilesystems = [ "zfs" ];
+    };
+
+    extraConfig = ''
+      # Using by-uuid overrides the default of by-id, and is unique
+      # to the qemu disks, as they don't produce by-id paths for
+      # some reason.
+      boot.zfs.devNodes = "/dev/disk/by-uuid/";
+      networking.hostId = "00000000";
+    '';
+
+    createPartitions = ''
+      machine.succeed(
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          + " mkpart primary ext2 1M 256MB"   # /boot
+          + " mkpart primary linux-swap 256MB 1280M"
+          + " mkpart primary ext2 1280M -1s", # /
+          "udevadm settle",
+
+          "mkswap /dev/vda2 -L swap",
+          "swapon -L swap",
+
+          "mkfs.ext4 -L nixos /dev/vda3",
+          "mount LABEL=nixos /mnt",
+
+          # Use as many ZFS features as possible to verify that GRUB can handle them
+          "zpool create"
+            " -o compatibility=grub2"
+            " -O utf8only=on"
+            " -O normalization=formD"
+            " -O compression=lz4"      # Activate the lz4_compress feature
+            " -O xattr=sa"
+            " -O acltype=posixacl"
+            " bpool /dev/vda1",
+          "zfs create"
+            " -o recordsize=1M"        # Prepare activating the large_blocks feature
+            " -o mountpoint=legacy"
+            " -o relatime=on"
+            " -o quota=1G"
+            " -o filesystem_limit=100" # Activate the filesystem_limits features
+            " bpool/boot",
+
+          # Snapshotting the top-level dataset would trigger a bug in GRUB2: https://github.com/openzfs/zfs/issues/13873
+          "zfs snapshot bpool/boot@snap-1",                     # Prepare activating the livelist and bookmarks features
+          "zfs clone bpool/boot@snap-1 bpool/test",             # Activate the livelist feature
+          "zfs bookmark bpool/boot@snap-1 bpool/boot#bookmark", # Activate the bookmarks feature
+          "zpool checkpoint bpool",                             # Activate the zpool_checkpoint feature
+          "mkdir -p /mnt/boot",
+          "mount -t zfs bpool/boot /mnt/boot",
+          "touch /mnt/boot/empty",                              # Activate zilsaxattr feature
+          "dd if=/dev/urandom of=/mnt/boot/test bs=1M count=1", # Activate the large_blocks feature
+
+          # Print out all enabled and active ZFS features (and some other stuff)
+          "sync /mnt/boot",
+          "zpool get all bpool >&2",
+
+          # Abort early if GRUB2 doesn't like the disks
+          "grub-probe --target=device /mnt/boot >&2",
+      )
+    '';
+
+    # umount & export bpool before shutdown
+    # this is a fix for "cannot import 'bpool': pool was previously in use from another system."
+    postInstallCommands = ''
+      machine.succeed("umount /mnt/boot")
+      machine.succeed("zpool export bpool")
+    '';
+  };
+
   # zfs on / with swap
   zfsroot = makeInstallerTest "zfs-root" {
     extraInstallerConfig = {
@@ -682,14 +966,21 @@ in {
     createPartitions = ''
       machine.succeed(
           "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
-          + " mkpart primary linux-swap 1M 1024M"
-          + " mkpart primary 1024M -1s",
+          + " mkpart primary 1M 100MB"  # /boot
+          + " mkpart primary linux-swap 100M 1024M"
+          + " mkpart primary 1024M -1s", # rpool
           "udevadm settle",
-          "mkswap /dev/vda1 -L swap",
+          "mkswap /dev/vda2 -L swap",
           "swapon -L swap",
-          "zpool create rpool /dev/vda2",
+          "zpool create rpool /dev/vda3",
           "zfs create -o mountpoint=legacy rpool/root",
           "mount -t zfs rpool/root /mnt",
+          "zfs create -o mountpoint=legacy rpool/root/usr",
+          "mkdir /mnt/usr",
+          "mount -t zfs rpool/root/usr /mnt/usr",
+          "mkfs.vfat -n BOOT /dev/vda1",
+          "mkdir /mnt/boot",
+          "mount LABEL=BOOT /mnt/boot",
           "udevadm settle",
       )
     '';
@@ -762,7 +1053,7 @@ in {
         encrypted.enable = true;
         encrypted.blkDev = "/dev/vda3";
         encrypted.label = "crypt";
-        encrypted.keyFile = "/mnt-root/keyfile";
+        encrypted.keyFile = "/${if systemdStage1 then "sysroot" else "mnt-root"}/keyfile";
       };
     '';
   };
@@ -918,6 +1209,10 @@ in {
     enableOCR = true;
     preBootCommands = ''
       machine.start()
+      # Enter it wrong once
+      machine.wait_for_text("enter passphrase for ")
+      machine.send_chars("wrong\n")
+      # Then enter it right.
       machine.wait_for_text("enter passphrase for ")
       machine.send_chars("password\n")
     '';
@@ -931,9 +1226,8 @@ in {
         "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",
+        "echo password | bcachefs unlock -k session /dev/vda3",
         "echo password | mount -t bcachefs /dev/vda3 /mnt",
         "mkfs.ext3 -L boot /dev/vda1",
         "mkdir -p /mnt/boot",
@@ -1091,6 +1385,13 @@ in {
       )
     '';
   };
+} // {
+  clevisBcachefs = mkClevisBcachefsTest { };
+  clevisBcachefsFallback = mkClevisBcachefsTest { fallback = true; };
+  clevisLuks = mkClevisLuksTest { };
+  clevisLuksFallback = mkClevisLuksTest { fallback = true; };
+  clevisZfs = mkClevisZfsTest { };
+  clevisZfsFallback = mkClevisZfsTest { fallback = true; };
 } // optionalAttrs systemdStage1 {
   stratisRoot = makeInstallerTest "stratisRoot" {
     createPartitions = ''
diff --git a/nixos/tests/invidious.nix b/nixos/tests/invidious.nix
index 582d1550fff1a..e31cd87f6a004 100644
--- a/nixos/tests/invidious.nix
+++ b/nixos/tests/invidious.nix
@@ -5,49 +5,72 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     maintainers = [ sbruder ];
   };
 
-  nodes.machine = { config, lib, pkgs, ... }: {
-    services.invidious = {
-      enable = true;
+  nodes = {
+    postgres-tcp = { config, pkgs, ... }: {
+      services.postgresql = {
+        enable = true;
+        initialScript = pkgs.writeText "init-postgres-with-password" ''
+          CREATE USER invidious WITH PASSWORD 'correct horse battery staple';
+          CREATE DATABASE invidious WITH OWNER invidious;
+        '';
+        enableTCPIP = true;
+        authentication = ''
+          host invidious invidious samenet scram-sha-256
+        '';
+      };
+      networking.firewall.allowedTCPPorts = [ config.services.postgresql.port ];
     };
+    machine = { config, lib, pkgs, ... }: {
+      services.invidious = {
+        enable = true;
+      };
 
-    specialisation = {
-      nginx.configuration = {
-        services.invidious = {
-          nginx.enable = true;
-          domain = "invidious.example.com";
-        };
-        services.nginx.virtualHosts."invidious.example.com" = {
-          forceSSL = false;
-          enableACME = false;
+      specialisation = {
+        nginx.configuration = {
+          services.invidious = {
+            nginx.enable = true;
+            domain = "invidious.example.com";
+          };
+          services.nginx.virtualHosts."invidious.example.com" = {
+            forceSSL = false;
+            enableACME = false;
+          };
+          networking.hosts."127.0.0.1" = [ "invidious.example.com" ];
         };
-        networking.hosts."127.0.0.1" = [ "invidious.example.com" ];
-      };
-      postgres-tcp.configuration = {
-        services.invidious = {
-          database = {
-            createLocally = false;
-            host = "127.0.0.1";
-            passwordFile = toString (pkgs.writeText "database-password" "correct horse battery staple");
+        nginx-scale.configuration = {
+          services.invidious = {
+            nginx.enable = true;
+            domain = "invidious.example.com";
+            serviceScale = 3;
+          };
+          services.nginx.virtualHosts."invidious.example.com" = {
+            forceSSL = false;
+            enableACME = false;
           };
+          networking.hosts."127.0.0.1" = [ "invidious.example.com" ];
         };
-        # Normally not needed because when connecting to postgres over TCP/IP
-        # the database is most likely on another host.
-        systemd.services.invidious = {
-          after = [ "postgresql.service" ];
-          requires = [ "postgresql.service" ];
+        nginx-scale-ytproxy.configuration = {
+          services.invidious = {
+            nginx.enable = true;
+            http3-ytproxy.enable = true;
+            domain = "invidious.example.com";
+            serviceScale = 3;
+          };
+          services.nginx.virtualHosts."invidious.example.com" = {
+            forceSSL = false;
+            enableACME = false;
+          };
+          networking.hosts."127.0.0.1" = [ "invidious.example.com" ];
         };
-        services.postgresql =
-          let
-            inherit (config.services.invidious.settings.db) dbname user;
-          in
-          {
-            enable = true;
-            initialScript = pkgs.writeText "init-postgres-with-password" ''
-              CREATE USER kemal WITH PASSWORD 'correct horse battery staple';
-              CREATE DATABASE invidious;
-              GRANT ALL PRIVILEGES ON DATABASE invidious TO kemal;
-            '';
+        postgres-tcp.configuration = {
+          services.invidious = {
+            database = {
+              createLocally = false;
+              host = "postgres-tcp";
+              passwordFile = toString (pkgs.writeText "database-password" "correct horse battery staple");
+            };
           };
+        };
       };
     };
   };
@@ -64,6 +87,9 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     url = "http://localhost:${toString nodes.machine.config.services.invidious.port}"
     port = ${toString nodes.machine.config.services.invidious.port}
 
+    # start postgres vm now
+    postgres_tcp.start()
+
     machine.wait_for_open_port(port)
     curl_assert_status_code(f"{url}/search", 200)
 
@@ -71,9 +97,26 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     machine.wait_for_open_port(80)
     curl_assert_status_code("http://invidious.example.com/search", 200)
 
-    # Remove the state so the `initialScript` gets run
-    machine.succeed("systemctl stop postgresql")
-    machine.succeed("rm -r /var/lib/postgresql")
+    activate_specialisation("nginx-scale")
+    machine.wait_for_open_port(80)
+    # this depends on nginx round-robin behaviour for the upstream servers
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+    machine.succeed("journalctl -eu invidious.service | grep -o '200 GET /search'")
+    machine.succeed("journalctl -eu invidious-1.service | grep -o '200 GET /search'")
+    machine.succeed("journalctl -eu invidious-2.service | grep -o '200 GET /search'")
+
+    activate_specialisation("nginx-scale-ytproxy")
+    machine.wait_for_unit("http3-ytproxy.service")
+    machine.wait_for_open_port(80)
+    machine.wait_until_succeeds("ls /run/http3-ytproxy/socket/http-proxy.sock")
+    curl_assert_status_code("http://invidious.example.com/search", 200)
+    # this should error out as no internet connectivity is available in the test
+    curl_assert_status_code("http://invidious.example.com/vi/dQw4w9WgXcQ/mqdefault.jpg", 502)
+    machine.succeed("journalctl -eu http3-ytproxy.service | grep -o 'dQw4w9WgXcQ'")
+
+    postgres_tcp.wait_for_unit("postgresql.service")
     activate_specialisation("postgres-tcp")
     machine.wait_for_open_port(port)
     curl_assert_status_code(f"{url}/search", 200)
diff --git a/nixos/tests/invoiceplane.nix b/nixos/tests/invoiceplane.nix
index 70ed96ee39f35..0b51707171996 100644
--- a/nixos/tests/invoiceplane.nix
+++ b/nixos/tests/invoiceplane.nix
@@ -27,56 +27,80 @@ import ./make-test-python.nix ({ pkgs, ... }:
       networking.firewall.allowedTCPPorts = [ 80 ];
       networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
+
+    invoiceplane_nginx = { ... }: {
+      services.invoiceplane.webserver = "nginx";
+      services.invoiceplane.sites = {
+        "site1.local" = {
+          database.name = "invoiceplane1";
+          database.createLocally = true;
+          enable = true;
+        };
+        "site2.local" = {
+          database.name = "invoiceplane2";
+          database.createLocally = true;
+          enable = true;
+        };
+      };
+
+      networking.firewall.allowedTCPPorts = [ 80 ];
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+    };
   };
 
   testScript = ''
     start_all()
 
     invoiceplane_caddy.wait_for_unit("caddy")
-    invoiceplane_caddy.wait_for_open_port(80)
-    invoiceplane_caddy.wait_for_open_port(3306)
+    invoiceplane_nginx.wait_for_unit("nginx")
 
     site_names = ["site1.local", "site2.local"]
 
-    for site_name in site_names:
-        machine.wait_for_unit(f"phpfpm-invoiceplane-{site_name}")
+    machines = [invoiceplane_caddy, invoiceplane_nginx]
+
+    for machine in machines:
+      machine.wait_for_open_port(80)
+      machine.wait_for_open_port(3306)
+
+      for site_name in site_names:
+          machine.wait_for_unit(f"phpfpm-invoiceplane-{site_name}")
 
-        with subtest("Website returns welcome screen"):
-            assert "Please install InvoicePlane" in machine.succeed(f"curl -L {site_name}")
+          with subtest("Website returns welcome screen"):
+              assert "Please install InvoicePlane" in machine.succeed(f"curl -L {site_name}")
 
-        with subtest("Finish InvoicePlane setup"):
-          machine.succeed(
-            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}/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}/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}/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}/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}/setup/upgrade_tables"
-          )
+          with subtest("Finish InvoicePlane setup"):
+            machine.succeed(
+              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}/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}/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}/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}/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}/setup/upgrade_tables"
+            )
   '';
 })
diff --git a/nixos/tests/iscsi-multipath-root.nix b/nixos/tests/iscsi-multipath-root.nix
index 92ae9990c9477..494a539b57e00 100644
--- a/nixos/tests/iscsi-multipath-root.nix
+++ b/nixos/tests/iscsi-multipath-root.nix
@@ -202,7 +202,7 @@ import ./make-test-python.nix (
       initiatorAuto.succeed("umount /mnt")
 
       initiatorAuto.succeed("systemctl restart multipathd")
-      initiatorAuto.succeed("multipath -ll | systemd-cat")
+      initiatorAuto.succeed("systemd-cat multipath -ll")
 
       # Install our RootDisk machine to 123456, the alias to the device that multipath is now managing
       initiatorAuto.succeed("mount /dev/mapper/123456 /mnt")
@@ -223,7 +223,7 @@ import ./make-test-python.nix (
       initiatorRootDisk.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.1.3 --login")
       initiatorRootDisk.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login")
       initiatorRootDisk.succeed("systemctl restart multipathd")
-      initiatorRootDisk.succeed("multipath -ll | systemd-cat")
+      initiatorRootDisk.succeed("systemd-cat multipath -ll")
 
       # Verify we can write and sync the root disk
       initiatorRootDisk.succeed("mkdir /scratch")
diff --git a/nixos/tests/iscsi-root.nix b/nixos/tests/iscsi-root.nix
index eb0719edc3796..0d7c48464eecc 100644
--- a/nixos/tests/iscsi-root.nix
+++ b/nixos/tests/iscsi-root.nix
@@ -7,8 +7,8 @@ import ./make-test-python.nix (
       {
         name = "iscsi";
         meta = {
-          maintainers = pkgs.lib.teams.deshaw.members
-          ++ (with pkgs.lib.maintainers; [ ajs124 ]);
+          maintainers = lib.teams.deshaw.members
+            ++ lib.teams.helsinki-systems.members;
         };
 
         nodes = {
diff --git a/nixos/tests/jitsi-meet.nix b/nixos/tests/jitsi-meet.nix
index c39cd19e1f0a7..fc6654f2c076c 100644
--- a/nixos/tests/jitsi-meet.nix
+++ b/nixos/tests/jitsi-meet.nix
@@ -24,10 +24,23 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       security.acme.acceptTerms = true;
       security.acme.defaults.email = "me@example.org";
       security.acme.defaults.server = "https://example.com"; # self-signed only
+
+      specialisation.caddy = {
+        inheritParentConfig = true;
+        configuration = {
+          services.jitsi-meet = {
+            caddy.enable = true;
+            nginx.enable = false;
+          };
+          services.caddy.virtualHosts.${config.services.jitsi-meet.hostName}.extraConfig = ''
+            tls internal
+          '';
+        };
+      };
     };
   };
 
-  testScript = ''
+  testScript = { nodes, ... }: ''
     server.wait_for_unit("jitsi-videobridge2.service")
     server.wait_for_unit("jicofo.service")
     server.wait_for_unit("nginx.service")
@@ -41,6 +54,15 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     )
 
     client.wait_for_unit("network.target")
-    assert "<title>Jitsi Meet</title>" in client.succeed("curl -sSfkL http://server/")
+
+    def client_curl():
+        assert "<title>Jitsi Meet</title>" in client.succeed("curl -sSfkL http://server/")
+
+    client_curl()
+
+    with subtest("Testing backup service"):
+        server.succeed("${nodes.server.system.build.toplevel}/specialisation/caddy/bin/switch-to-configuration test")
+        server.wait_for_unit("caddy.service")
+        client_curl()
   '';
 })
diff --git a/nixos/tests/jool.nix b/nixos/tests/jool.nix
index 6d5ded9b18e07..93575f07b1c8c 100644
--- a/nixos/tests/jool.nix
+++ b/nixos/tests/jool.nix
@@ -1,9 +1,4 @@
-{ system ? builtins.currentSystem,
-  config ? {},
-  pkgs ? import ../.. { inherit system config; }
-}:
-
-with import ../lib/testing-python.nix { inherit system pkgs; };
+{ pkgs, runTest }:
 
 let
   inherit (pkgs) lib;
@@ -23,7 +18,6 @@ let
       description = "Mock webserver";
       wants = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
-      serviceConfig.Restart = "always";
       script = ''
         while true; do
         {
@@ -40,7 +34,7 @@ let
 in
 
 {
-  siit = makeTest {
+  siit = runTest {
     # This test simulates the setup described in [1] with two IPv6 and
     # IPv4-only devices on different subnets communicating through a border
     # relay running Jool in SIIT mode.
@@ -49,8 +43,7 @@ in
     meta.maintainers = with lib.maintainers; [ rnhmjoj ];
 
     # Border relay
-    nodes.relay = { ... }: {
-      imports = [ ../modules/profiles/minimal.nix ];
+    nodes.relay = {
       virtualisation.vlans = [ 1 2 ];
 
       # Enable packet routing
@@ -65,20 +58,13 @@ in
         eth2.ipv4.addresses = [ { address = "192.0.2.1";  prefixLength = 24; } ];
       };
 
-      networking.jool = {
-        enable = true;
-        siit.enable = true;
-        siit.config.global.pool6 = "fd::/96";
-      };
+      networking.jool.enable = true;
+      networking.jool.siit.default.global.pool6 = "fd::/96";
     };
 
     # IPv6 only node
-    nodes.alice = { ... }: {
-      imports = [
-        ../modules/profiles/minimal.nix
-        ipv6Only
-        (webserver 6 "Hello, Bob!")
-      ];
+    nodes.alice = {
+      imports = [ ipv6Only (webserver 6 "Hello, Bob!") ];
 
       virtualisation.vlans = [ 1 ];
       networking.interfaces.eth1.ipv6 = {
@@ -89,12 +75,8 @@ in
     };
 
     # IPv4 only node
-    nodes.bob = { ... }: {
-      imports = [
-        ../modules/profiles/minimal.nix
-        ipv4Only
-        (webserver 4 "Hello, Alice!")
-      ];
+    nodes.bob = {
+      imports = [ ipv4Only (webserver 4 "Hello, Alice!") ];
 
       virtualisation.vlans = [ 2 ];
       networking.interfaces.eth1.ipv4 = {
@@ -107,17 +89,17 @@ in
     testScript = ''
       start_all()
 
-      relay.wait_for_unit("jool-siit.service")
+      relay.wait_for_unit("jool-siit-default.service")
       alice.wait_for_unit("network-addresses-eth1.service")
       bob.wait_for_unit("network-addresses-eth1.service")
 
       with subtest("Alice and Bob can't ping each other"):
-        relay.systemctl("stop jool-siit.service")
+        relay.systemctl("stop jool-siit-default.service")
         alice.fail("ping -c1 fd::192.0.2.16")
         bob.fail("ping -c1 198.51.100.8")
 
       with subtest("Alice and Bob can ping using the relay"):
-        relay.systemctl("start jool-siit.service")
+        relay.systemctl("start jool-siit-default.service")
         alice.wait_until_succeeds("ping -c1 fd::192.0.2.16")
         bob.wait_until_succeeds("ping -c1 198.51.100.8")
 
@@ -132,7 +114,7 @@ in
     '';
   };
 
-  nat64 = makeTest {
+  nat64 = runTest {
     # This test simulates the setup described in [1] with two IPv6-only nodes
     # (a client and a homeserver) on the LAN subnet and an IPv4 node on the WAN.
     # The router runs Jool in stateful NAT64 mode, masquarading the LAN and
@@ -142,8 +124,7 @@ in
     meta.maintainers = with lib.maintainers; [ rnhmjoj ];
 
     # Router
-    nodes.router = { ... }: {
-      imports = [ ../modules/profiles/minimal.nix ];
+    nodes.router = {
       virtualisation.vlans = [ 1 2 ];
 
       # Enable packet routing
@@ -158,32 +139,29 @@ in
         eth2.ipv4.addresses = [ { address = "203.0.113.1"; prefixLength = 24; } ];
       };
 
-      networking.jool = {
-        enable = true;
-        nat64.enable = true;
-        nat64.config = {
-          bib = [
-            { # forward HTTP 203.0.113.1 (router) → 2001:db8::9 (homeserver)
-              "protocol"     = "TCP";
-              "ipv4 address" = "203.0.113.1#80";
-              "ipv6 address" = "2001:db8::9#80";
-            }
-          ];
-          pool4 = [
-            # Ports for dynamic translation
-            { protocol =  "TCP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
-            { protocol =  "UDP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
-            { protocol = "ICMP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
-            # Ports for static BIB entries
-            { protocol =  "TCP";  prefix = "203.0.113.1/32"; "port range" = "80"; }
-          ];
-        };
+      networking.jool.enable = true;
+      networking.jool.nat64.default = {
+        bib = [
+          { # forward HTTP 203.0.113.1 (router) → 2001:db8::9 (homeserver)
+            "protocol"     = "TCP";
+            "ipv4 address" = "203.0.113.1#80";
+            "ipv6 address" = "2001:db8::9#80";
+          }
+        ];
+        pool4 = [
+          # Ports for dynamic translation
+          { protocol =  "TCP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+          { protocol =  "UDP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+          { protocol = "ICMP";  prefix = "203.0.113.1/32"; "port range" = "40001-65535"; }
+          # Ports for static BIB entries
+          { protocol =  "TCP";  prefix = "203.0.113.1/32"; "port range" = "80"; }
+        ];
       };
     };
 
     # LAN client (IPv6 only)
-    nodes.client = { ... }: {
-      imports = [ ../modules/profiles/minimal.nix ipv6Only ];
+    nodes.client = {
+      imports = [ ipv6Only ];
       virtualisation.vlans = [ 1 ];
 
       networking.interfaces.eth1.ipv6 = {
@@ -194,12 +172,8 @@ in
     };
 
     # LAN server (IPv6 only)
-    nodes.homeserver = { ... }: {
-      imports = [
-        ../modules/profiles/minimal.nix
-        ipv6Only
-        (webserver 6 "Hello from IPv6!")
-      ];
+    nodes.homeserver = {
+      imports = [ ipv6Only (webserver 6 "Hello from IPv6!") ];
 
       virtualisation.vlans = [ 1 ];
       networking.interfaces.eth1.ipv6 = {
@@ -210,12 +184,8 @@ in
     };
 
     # WAN server (IPv4 only)
-    nodes.server = { ... }: {
-      imports = [
-        ../modules/profiles/minimal.nix
-        ipv4Only
-        (webserver 4 "Hello from IPv4!")
-      ];
+    nodes.server = {
+      imports = [ ipv4Only (webserver 4 "Hello from IPv4!") ];
 
       virtualisation.vlans = [ 2 ];
       networking.interfaces.eth1.ipv4.addresses =
@@ -229,7 +199,7 @@ in
         node.wait_for_unit("network-addresses-eth1.service")
 
       with subtest("Client can ping the WAN server"):
-        router.wait_for_unit("jool-nat64.service")
+        router.wait_for_unit("jool-nat64-default.service")
         client.succeed("ping -c1 64:ff9b::203.0.113.16")
 
       with subtest("Client can connect to the WAN webserver"):
diff --git a/nixos/tests/kafka.nix b/nixos/tests/kafka.nix
index 864253fd8c73b..f4f9827ab7b5f 100644
--- a/nixos/tests/kafka.nix
+++ b/nixos/tests/kafka.nix
@@ -6,13 +6,62 @@
 with pkgs.lib;
 
 let
-  makeKafkaTest = name: kafkaPackage: (import ./make-test-python.nix ({
+  makeKafkaTest = name: { kafkaPackage, mode ? "zookeeper" }: (import ./make-test-python.nix ({
     inherit name;
     meta = with pkgs.lib.maintainers; {
       maintainers = [ nequissimus ];
     };
 
     nodes = {
+      kafka = { ... }: {
+        services.apache-kafka = mkMerge [
+          ({
+            enable = true;
+            package = kafkaPackage;
+            settings = {
+              "offsets.topic.replication.factor" = 1;
+              "log.dirs" = [
+                "/var/lib/kafka/logdir1"
+                "/var/lib/kafka/logdir2"
+              ];
+            };
+          })
+          (mkIf (mode == "zookeeper") {
+            settings = {
+              "zookeeper.session.timeout.ms" = 600000;
+              "zookeeper.connect" = [ "zookeeper1:2181" ];
+            };
+          })
+          (mkIf (mode == "kraft") {
+            clusterId = "ak2fIHr4S8WWarOF_ODD0g";
+            formatLogDirs = true;
+            settings = {
+              "node.id" = 1;
+              "process.roles" = [
+                "broker"
+                "controller"
+              ];
+              "listeners" = [
+                "PLAINTEXT://:9092"
+                "CONTROLLER://:9093"
+              ];
+              "listener.security.protocol.map" = [
+                "PLAINTEXT:PLAINTEXT"
+                "CONTROLLER:PLAINTEXT"
+              ];
+              "controller.quorum.voters" = [
+                "1@kafka:9093"
+              ];
+              "controller.listener.names" = [ "CONTROLLER" ];
+            };
+          })
+        ];
+
+        networking.firewall.allowedTCPPorts = [ 9092 9093 ];
+        # i686 tests: qemu-system-i386 can simulate max 2047MB RAM (not 2048)
+        virtualisation.memorySize = 2047;
+      };
+    } // optionalAttrs (mode == "zookeeper") {
       zookeeper1 = { ... }: {
         services.zookeeper = {
           enable = true;
@@ -20,29 +69,16 @@ let
 
         networking.firewall.allowedTCPPorts = [ 2181 ];
       };
-      kafka = { ... }: {
-        services.apache-kafka = {
-          enable = true;
-          extraProperties = ''
-            offsets.topic.replication.factor = 1
-            zookeeper.session.timeout.ms = 600000
-          '';
-          package = kafkaPackage;
-          zookeeper = "zookeeper1:2181";
-        };
-
-        networking.firewall.allowedTCPPorts = [ 9092 ];
-        # i686 tests: qemu-system-i386 can simulate max 2047MB RAM (not 2048)
-        virtualisation.memorySize = 2047;
-      };
     };
 
     testScript = ''
       start_all()
 
+      ${optionalString (mode == "zookeeper") ''
       zookeeper1.wait_for_unit("default.target")
       zookeeper1.wait_for_unit("zookeeper.service")
       zookeeper1.wait_for_open_port(2181)
+      ''}
 
       kafka.wait_for_unit("default.target")
       kafka.wait_for_unit("apache-kafka.service")
@@ -67,12 +103,13 @@ let
   }) { inherit system; });
 
 in with pkgs; {
-  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_3_4  = makeKafkaTest "kafka_3_4"  apacheKafka_3_4;
-  kafka_3_5  = makeKafkaTest "kafka_3_5"  apacheKafka_3_5;
-  kafka  = makeKafkaTest "kafka"  apacheKafka;
+  kafka_2_8 = makeKafkaTest "kafka_2_8" { kafkaPackage = apacheKafka_2_8; };
+  kafka_3_0 = makeKafkaTest "kafka_3_0" { kafkaPackage = apacheKafka_3_0; };
+  kafka_3_1 = makeKafkaTest "kafka_3_1" { kafkaPackage = apacheKafka_3_1; };
+  kafka_3_2 = makeKafkaTest "kafka_3_2" { kafkaPackage = apacheKafka_3_2; };
+  kafka_3_3 = makeKafkaTest "kafka_3_3" { kafkaPackage = apacheKafka_3_3; };
+  kafka_3_4 = makeKafkaTest "kafka_3_4" { kafkaPackage = apacheKafka_3_4; };
+  kafka_3_5 = makeKafkaTest "kafka_3_5" { kafkaPackage = apacheKafka_3_5; };
+  kafka = makeKafkaTest "kafka" { kafkaPackage = apacheKafka; };
+  kafka_kraft = makeKafkaTest "kafka_kraft" { kafkaPackage = apacheKafka; mode = "kraft"; };
 }
diff --git a/nixos/tests/kanidm.nix b/nixos/tests/kanidm.nix
index 3f5bca397740e..fa24d4a8a5e13 100644
--- a/nixos/tests/kanidm.nix
+++ b/nixos/tests/kanidm.nix
@@ -67,6 +67,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       ''
         start_all()
         server.wait_for_unit("kanidm.service")
+        client.systemctl("start network-online.target")
         client.wait_for_unit("network-online.target")
 
         with subtest("Test HTTP interface"):
diff --git a/nixos/tests/kea.nix b/nixos/tests/kea.nix
index b4095893b4825..c8ecf771fa13a 100644
--- a/nixos/tests/kea.nix
+++ b/nixos/tests/kea.nix
@@ -134,31 +134,32 @@ import ./make-test-python.nix ({ pkgs, lib, ...}: {
         extraArgs = [
           "-v"
         ];
-        extraConfig = ''
-          server:
-              listen: 0.0.0.0@53
-
-          log:
-            - target: syslog
-              any: debug
-
-          acl:
-            - id: dhcp_ddns
-              address: 10.0.0.1
-              action: update
-
-          template:
-            - id: default
-              storage: ${zonesDir}
-              zonefile-sync: -1
-              zonefile-load: difference-no-serial
-              journal-content: all
-
-          zone:
-            - domain: lan.nixos.test
-              file: lan.nixos.test.zone
-              acl: [dhcp_ddns]
-        '';
+        settings = {
+          server.listen = [
+            "0.0.0.0@53"
+          ];
+
+          log.syslog.any = "info";
+
+          acl.dhcp_ddns = {
+            address = "10.0.0.1";
+            action = "update";
+          };
+
+          template.default = {
+            storage = zonesDir;
+            zonefile-sync = "-1";
+            zonefile-load = "difference-no-serial";
+            journal-content = "all";
+          };
+
+          zone."lan.nixos.test" = {
+            file = "lan.nixos.test.zone";
+            acl = [
+              "dhcp_ddns"
+            ];
+          };
+        };
       };
 
     };
diff --git a/nixos/tests/keepalived.nix b/nixos/tests/keepalived.nix
index d0bf9d4652003..ce291514591fe 100644
--- a/nixos/tests/keepalived.nix
+++ b/nixos/tests/keepalived.nix
@@ -1,5 +1,6 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "keepalived";
+  maintainers = [ lib.maintainers.raitobezarius ];
 
   nodes = {
     node1 = { pkgs, ... }: {
diff --git a/nixos/tests/kerberos/heimdal.nix b/nixos/tests/kerberos/heimdal.nix
index 47f9d0285aef7..393289f7a92ca 100644
--- a/nixos/tests/kerberos/heimdal.nix
+++ b/nixos/tests/kerberos/heimdal.nix
@@ -1,5 +1,6 @@
 import ../make-test-python.nix ({pkgs, ...}: {
   name = "kerberos_server-heimdal";
+
   nodes.machine = { config, libs, pkgs, ...}:
   { services.kerberos_server =
     { enable = true;
@@ -7,16 +8,18 @@ import ../make-test-python.nix ({pkgs, ...}: {
         "FOO.BAR".acl = [{principal = "admin"; access = ["add" "cpw"];}];
       };
     };
-    krb5 = {
+    security.krb5 = {
       enable = true;
-      kerberos = pkgs.heimdal;
-      libdefaults = {
-        default_realm = "FOO.BAR";
-      };
-      realms = {
-        "FOO.BAR" = {
-          admin_server = "machine";
-          kdc = "machine";
+      package = pkgs.heimdal;
+      settings = {
+        libdefaults = {
+          default_realm = "FOO.BAR";
+        };
+        realms = {
+          "FOO.BAR" = {
+            admin_server = "machine";
+            kdc = "machine";
+          };
         };
       };
     };
@@ -39,4 +42,6 @@ import ../make-test-python.nix ({pkgs, ...}: {
         "kinit -kt alice.keytab alice",
     )
   '';
+
+  meta.maintainers = [ pkgs.lib.maintainers.dblsaiko ];
 })
diff --git a/nixos/tests/kerberos/mit.nix b/nixos/tests/kerberos/mit.nix
index 7e427ffef0ba8..1191d047abbf0 100644
--- a/nixos/tests/kerberos/mit.nix
+++ b/nixos/tests/kerberos/mit.nix
@@ -1,5 +1,6 @@
 import ../make-test-python.nix ({pkgs, ...}: {
   name = "kerberos_server-mit";
+
   nodes.machine = { config, libs, pkgs, ...}:
   { services.kerberos_server =
     { enable = true;
@@ -7,16 +8,18 @@ import ../make-test-python.nix ({pkgs, ...}: {
         "FOO.BAR".acl = [{principal = "admin"; access = ["add" "cpw"];}];
       };
     };
-    krb5 = {
+    security.krb5 = {
       enable = true;
-      kerberos = pkgs.krb5;
-      libdefaults = {
-        default_realm = "FOO.BAR";
-      };
-      realms = {
-        "FOO.BAR" = {
-          admin_server = "machine";
-          kdc = "machine";
+      package = pkgs.krb5;
+      settings = {
+        libdefaults = {
+          default_realm = "FOO.BAR";
+        };
+        realms = {
+          "FOO.BAR" = {
+            admin_server = "machine";
+            kdc = "machine";
+          };
         };
       };
     };
@@ -38,4 +41,6 @@ import ../make-test-python.nix ({pkgs, ...}: {
         "echo alice_pw | sudo -u alice kinit",
     )
   '';
+
+  meta.maintainers = [ pkgs.lib.maintainers.dblsaiko ];
 })
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index e69dd550289c1..72d31246b75d3 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -25,13 +25,13 @@ let
   }) args);
   kernels = pkgs.linuxKernel.vanillaPackages // {
     inherit (pkgs.linuxKernel.packages)
-      linux_4_14_hardened
       linux_4_19_hardened
       linux_5_4_hardened
       linux_5_10_hardened
       linux_5_15_hardened
       linux_6_1_hardened
-      linux_6_4_hardened
+      linux_6_5_hardened
+      linux_6_6_hardened
       linux_rt_5_4
       linux_rt_5_10
       linux_rt_5_15
diff --git a/nixos/tests/keyd.nix b/nixos/tests/keyd.nix
index 1ee08b4101f72..bfc4558b64bb4 100644
--- a/nixos/tests/keyd.nix
+++ b/nixos/tests/keyd.nix
@@ -26,13 +26,13 @@ let
   '';
 
 
-  mkKeyboardTest = name: { settings, test }: with pkgs.lib; makeTest {
+  mkKeyboardTest = name: { default, test }: with pkgs.lib; makeTest {
     inherit name;
 
     nodes.machine = {
       services.keyd = {
         enable = true;
-        keyboards.default = { inherit settings; };
+        keyboards = { inherit default; };
       };
     };
 
@@ -70,13 +70,20 @@ let
 in
 pkgs.lib.mapAttrs mkKeyboardTest {
   swap-ab_and_ctrl-as-shift = {
-    test.press = [ "a" "ctrl-b" "c" ];
-    test.expect = [ "b" "A" "c" ];
+    test.press = [ "a" "ctrl-b" "c" "alt_r-h" ];
+    test.expect = [ "b" "A" "c" "q" ];
 
-    settings.main = {
-      "a" = "b";
-      "b" = "a";
-      "control" = "oneshot(shift)";
+    default = {
+      settings.main = {
+        "a" = "b";
+        "b" = "a";
+        "control" = "oneshot(shift)";
+        "rightalt" = "layer(rightalt)";
+      };
+      extraConfig = ''
+        [rightalt:G]
+        h = q
+      '';
     };
   };
 }
diff --git a/nixos/tests/keymap.nix b/nixos/tests/keymap.nix
index cc45824667edb..e8973a50f8524 100644
--- a/nixos/tests/keymap.nix
+++ b/nixos/tests/keymap.nix
@@ -31,7 +31,7 @@ let
 
     nodes.machine.console.keyMap = mkOverride 900 layout;
     nodes.machine.services.xserver.desktopManager.xterm.enable = false;
-    nodes.machine.services.xserver.layout = mkOverride 900 layout;
+    nodes.machine.services.xserver.xkb.layout = mkOverride 900 layout;
     nodes.machine.imports = [ ./common/x11.nix extraConfig ];
 
     testScript = ''
@@ -116,7 +116,7 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
     };
 
     extraConfig.console.keyMap = "fr";
-    extraConfig.services.xserver.layout = "fr";
+    extraConfig.services.xserver.xkb.layout = "fr";
   };
 
   bone = {
@@ -130,8 +130,8 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
     };
 
     extraConfig.console.keyMap = "bone";
-    extraConfig.services.xserver.layout = "de";
-    extraConfig.services.xserver.xkbVariant = "bone";
+    extraConfig.services.xserver.xkb.layout = "de";
+    extraConfig.services.xserver.xkb.variant = "bone";
   };
 
   colemak = {
@@ -141,8 +141,8 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
     };
 
     extraConfig.console.keyMap = "colemak";
-    extraConfig.services.xserver.layout = "us";
-    extraConfig.services.xserver.xkbVariant = "colemak";
+    extraConfig.services.xserver.xkb.layout = "us";
+    extraConfig.services.xserver.xkb.variant = "colemak";
   };
 
   dvorak = {
@@ -154,8 +154,8 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
     };
 
     extraConfig.console.keyMap = "dvorak";
-    extraConfig.services.xserver.layout = "us";
-    extraConfig.services.xserver.xkbVariant = "dvorak";
+    extraConfig.services.xserver.xkb.layout = "us";
+    extraConfig.services.xserver.xkb.variant = "dvorak";
   };
 
   dvorak-programmer = {
@@ -170,8 +170,8 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
     };
 
     extraConfig.console.keyMap = "dvorak-programmer";
-    extraConfig.services.xserver.layout = "us";
-    extraConfig.services.xserver.xkbVariant = "dvp";
+    extraConfig.services.xserver.xkb.layout = "us";
+    extraConfig.services.xserver.xkb.variant = "dvp";
   };
 
   neo = {
@@ -185,8 +185,8 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
     };
 
     extraConfig.console.keyMap = "neo";
-    extraConfig.services.xserver.layout = "de";
-    extraConfig.services.xserver.xkbVariant = "neo";
+    extraConfig.services.xserver.xkb.layout = "de";
+    extraConfig.services.xserver.xkb.variant = "neo";
   };
 
   qwertz = {
@@ -199,7 +199,7 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
     };
 
     extraConfig.console.keyMap = "de";
-    extraConfig.services.xserver.layout = "de";
+    extraConfig.services.xserver.xkb.layout = "de";
   };
 
   custom = {
@@ -212,8 +212,8 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
     };
 
     extraConfig.console.useXkbConfig = true;
-    extraConfig.services.xserver.layout = "us-greek";
-    extraConfig.services.xserver.extraLayouts.us-greek =
+    extraConfig.services.xserver.xkb.layout = "us-greek";
+    extraConfig.services.xserver.xkb.extraLayouts.us-greek =
       { description = "US layout with alt-gr greek";
         languages   = [ "eng" ];
         symbolsFile = pkgs.writeText "us-greek" ''
diff --git a/nixos/tests/knot.nix b/nixos/tests/knot.nix
index 2ecbf69194bb7..44efd93b6fa95 100644
--- a/nixos/tests/knot.nix
+++ b/nixos/tests/knot.nix
@@ -60,44 +60,43 @@ in {
       services.knot.enable = true;
       services.knot.extraArgs = [ "-v" ];
       services.knot.keyFiles = [ tsigFile ];
-      services.knot.extraConfig = ''
-        server:
-            listen: 0.0.0.0@53
-            listen: ::@53
-            automatic-acl: true
-
-        remote:
-          - id: secondary
-            address: 192.168.0.2@53
-            key: xfr_key
-
-        template:
-          - id: default
-            storage: ${knotZonesEnv}
-            notify: [secondary]
-            dnssec-signing: on
-            # Input-only zone files
-            # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
-            # prevents modification of the zonefiles, since the zonefiles are immutable
-            zonefile-sync: -1
-            zonefile-load: difference
-            journal-content: changes
-            # move databases below the state directory, because they need to be writable
-            journal-db: /var/lib/knot/journal
-            kasp-db: /var/lib/knot/kasp
-            timer-db: /var/lib/knot/timer
-
-        zone:
-          - domain: example.com
-            file: example.com.zone
-
-          - domain: sub.example.com
-            file: sub.example.com.zone
-
-        log:
-          - target: syslog
-            any: info
-      '';
+      services.knot.settings = {
+        server = {
+          listen = [
+            "0.0.0.0@53"
+            "::@53"
+           ];
+          automatic-acl = true;
+        };
+
+        acl.secondary_acl = {
+          address = "192.168.0.2";
+          key = "xfr_key";
+          action = "transfer";
+        };
+
+        remote.secondary.address = "192.168.0.2@53";
+
+        template.default = {
+          storage = knotZonesEnv;
+          notify = [ "secondary" ];
+          acl = [ "secondary_acl" ];
+          dnssec-signing = true;
+          # Input-only zone files
+          # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-3
+          # prevents modification of the zonefiles, since the zonefiles are immutable
+          zonefile-sync = -1;
+          zonefile-load = "difference";
+          journal-content = "changes";
+        };
+
+        zone = {
+          "example.com".file = "example.com.zone";
+          "sub.example.com".file = "sub.example.com.zone";
+        };
+
+        log.syslog.any = "info";
+      };
     };
 
     secondary = { lib, ... }: {
@@ -113,41 +112,36 @@ in {
       services.knot.enable = true;
       services.knot.keyFiles = [ tsigFile ];
       services.knot.extraArgs = [ "-v" ];
-      services.knot.extraConfig = ''
-        server:
-            listen: 0.0.0.0@53
-            listen: ::@53
-            automatic-acl: true
-
-        remote:
-          - id: primary
-            address: 192.168.0.1@53
-            key: xfr_key
-
-        template:
-          - id: default
-            master: primary
-            # zonefileless setup
-            # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
-            zonefile-sync: -1
-            zonefile-load: none
-            journal-content: all
-            # move databases below the state directory, because they need to be writable
-            journal-db: /var/lib/knot/journal
-            kasp-db: /var/lib/knot/kasp
-            timer-db: /var/lib/knot/timer
-
-        zone:
-          - domain: example.com
-            file: example.com.zone
-
-          - domain: sub.example.com
-            file: sub.example.com.zone
-
-        log:
-          - target: syslog
-            any: info
-      '';
+      services.knot.settings = {
+        server = {
+          listen = [
+            "0.0.0.0@53"
+            "::@53"
+          ];
+          automatic-acl = true;
+        };
+
+        remote.primary = {
+          address = "192.168.0.1@53";
+          key = "xfr_key";
+        };
+
+        template.default = {
+          master = "primary";
+          # zonefileless setup
+          # https://www.knot-dns.cz/docs/2.8/html/operation.html#example-2
+          zonefile-sync = "-1";
+          zonefile-load = "none";
+          journal-content = "all";
+        };
+
+        zone = {
+          "example.com".file = "example.com.zone";
+          "sub.example.com".file = "sub.example.com.zone";
+        };
+
+        log.syslog.any = "info";
+      };
     };
     client = { lib, nodes, ... }: {
       imports = [ common ];
diff --git a/nixos/tests/krb5/default.nix b/nixos/tests/krb5/default.nix
index dd5b2f37202e8..ede085632c634 100644
--- a/nixos/tests/krb5/default.nix
+++ b/nixos/tests/krb5/default.nix
@@ -1,5 +1,4 @@
 { system ? builtins.currentSystem }:
 {
   example-config = import ./example-config.nix { inherit system; };
-  deprecated-config = import ./deprecated-config.nix { inherit system; };
 }
diff --git a/nixos/tests/krb5/deprecated-config.nix b/nixos/tests/krb5/deprecated-config.nix
deleted file mode 100644
index aca29ae6ca2b2..0000000000000
--- a/nixos/tests/krb5/deprecated-config.nix
+++ /dev/null
@@ -1,50 +0,0 @@
-# Verifies that the configuration suggested in deprecated example values
-# will result in the expected output.
-
-import ../make-test-python.nix ({ pkgs, ...} : {
-  name = "krb5-with-deprecated-config";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ eqyiel ];
-  };
-
-  nodes.machine =
-    { ... }: {
-      krb5 = {
-        enable = true;
-        defaultRealm = "ATHENA.MIT.EDU";
-        domainRealm = "athena.mit.edu";
-        kdc = "kerberos.mit.edu";
-        kerberosAdminServer = "kerberos.mit.edu";
-      };
-    };
-
-  testScript =
-    let snapshot = pkgs.writeText "krb5-with-deprecated-config.conf" ''
-      [libdefaults]
-        default_realm = ATHENA.MIT.EDU
-
-      [realms]
-        ATHENA.MIT.EDU = {
-          admin_server = kerberos.mit.edu
-          kdc = kerberos.mit.edu
-        }
-
-      [domain_realm]
-        .athena.mit.edu = ATHENA.MIT.EDU
-        athena.mit.edu = ATHENA.MIT.EDU
-
-      [capaths]
-
-
-      [appdefaults]
-
-
-      [plugins]
-
-    '';
-  in ''
-    machine.succeed(
-        "diff /etc/krb5.conf ${snapshot}"
-    )
-  '';
-})
diff --git a/nixos/tests/krb5/example-config.nix b/nixos/tests/krb5/example-config.nix
index 9a5c3b2af2490..33bed481b39fc 100644
--- a/nixos/tests/krb5/example-config.nix
+++ b/nixos/tests/krb5/example-config.nix
@@ -4,78 +4,77 @@
 import ../make-test-python.nix ({ pkgs, ...} : {
   name = "krb5-with-example-config";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ eqyiel ];
+    maintainers = [ eqyiel dblsaiko ];
   };
 
   nodes.machine =
     { pkgs, ... }: {
-      krb5 = {
+      security.krb5 = {
         enable = true;
-        kerberos = pkgs.krb5;
-        libdefaults = {
-          default_realm = "ATHENA.MIT.EDU";
-        };
-        realms = {
-          "ATHENA.MIT.EDU" = {
-            admin_server = "athena.mit.edu";
-            kdc = [
-              "athena01.mit.edu"
-              "athena02.mit.edu"
-            ];
+        package = pkgs.krb5;
+        settings = {
+          includedir = [
+            "/etc/krb5.conf.d"
+          ];
+          include = [
+            "/etc/krb5-extra.conf"
+          ];
+          libdefaults = {
+            default_realm = "ATHENA.MIT.EDU";
           };
-        };
-        domain_realm = {
-          "example.com" = "EXAMPLE.COM";
-          ".example.com" = "EXAMPLE.COM";
-        };
-        capaths = {
-          "ATHENA.MIT.EDU" = {
-            "EXAMPLE.COM" = ".";
+          realms = {
+            "ATHENA.MIT.EDU" = {
+              admin_server = "athena.mit.edu";
+              kdc = [
+                "athena01.mit.edu"
+                "athena02.mit.edu"
+              ];
+            };
           };
-          "EXAMPLE.COM" = {
-            "ATHENA.MIT.EDU" = ".";
+          domain_realm = {
+            "example.com" = "EXAMPLE.COM";
+            ".example.com" = "EXAMPLE.COM";
           };
-        };
-        appdefaults = {
-          pam = {
-            debug = false;
-            ticket_lifetime = 36000;
-            renew_lifetime = 36000;
-            max_timeout = 30;
-            timeout_shift = 2;
-            initial_timeout = 1;
+          capaths = {
+            "ATHENA.MIT.EDU" = {
+              "EXAMPLE.COM" = ".";
+            };
+            "EXAMPLE.COM" = {
+              "ATHENA.MIT.EDU" = ".";
+            };
           };
-        };
-        plugins = {
-          ccselect = {
-            disable = "k5identity";
+          appdefaults = {
+            pam = {
+              debug = false;
+              ticket_lifetime = 36000;
+              renew_lifetime = 36000;
+              max_timeout = 30;
+              timeout_shift = 2;
+              initial_timeout = 1;
+            };
+          };
+          plugins.ccselect.disable = "k5identity";
+          logging = {
+            kdc = "SYSLOG:NOTICE";
+            admin_server = "SYSLOG:NOTICE";
+            default = "SYSLOG:NOTICE";
           };
         };
-        extraConfig = ''
-          [logging]
-            kdc          = SYSLOG:NOTICE
-            admin_server = SYSLOG:NOTICE
-            default      = SYSLOG:NOTICE
-        '';
       };
     };
 
   testScript =
     let snapshot = pkgs.writeText "krb5-with-example-config.conf" ''
-      [libdefaults]
-        default_realm = ATHENA.MIT.EDU
-
-      [realms]
-        ATHENA.MIT.EDU = {
-          admin_server = athena.mit.edu
-          kdc = athena01.mit.edu
-          kdc = athena02.mit.edu
+      [appdefaults]
+        pam = {
+          debug = false
+          initial_timeout = 1
+          max_timeout = 30
+          renew_lifetime = 36000
+          ticket_lifetime = 36000
+          timeout_shift = 2
         }
 
-      [domain_realm]
-        .example.com = EXAMPLE.COM
-        example.com = EXAMPLE.COM
-
       [capaths]
         ATHENA.MIT.EDU = {
           EXAMPLE.COM = .
@@ -84,25 +83,32 @@ import ../make-test-python.nix ({ pkgs, ...} : {
           ATHENA.MIT.EDU = .
         }
 
-      [appdefaults]
-        pam = {
-          debug = false
-          initial_timeout = 1
-          max_timeout = 30
-          renew_lifetime = 36000
-          ticket_lifetime = 36000
-          timeout_shift = 2
-        }
+      [domain_realm]
+        .example.com = EXAMPLE.COM
+        example.com = EXAMPLE.COM
+
+      [libdefaults]
+        default_realm = ATHENA.MIT.EDU
+
+      [logging]
+        admin_server = SYSLOG:NOTICE
+        default = SYSLOG:NOTICE
+        kdc = SYSLOG:NOTICE
 
       [plugins]
         ccselect = {
           disable = k5identity
         }
 
-      [logging]
-        kdc          = SYSLOG:NOTICE
-        admin_server = SYSLOG:NOTICE
-        default      = SYSLOG:NOTICE
+      [realms]
+        ATHENA.MIT.EDU = {
+          admin_server = athena.mit.edu
+          kdc = athena01.mit.edu
+          kdc = athena02.mit.edu
+        }
+
+      include /etc/krb5-extra.conf
+      includedir /etc/krb5.conf.d
     '';
   in ''
     machine.succeed(
diff --git a/nixos/tests/kubo/default.nix b/nixos/tests/kubo/default.nix
new file mode 100644
index 0000000000000..d8c0c69dc1fbd
--- /dev/null
+++ b/nixos/tests/kubo/default.nix
@@ -0,0 +1,7 @@
+{ recurseIntoAttrs, runTest }:
+recurseIntoAttrs {
+  kubo = runTest ./kubo.nix;
+  # The FUSE functionality is completely broken since Kubo v0.24.0
+  # See https://github.com/ipfs/kubo/issues/10242
+  # kubo-fuse = runTest ./kubo-fuse.nix;
+}
diff --git a/nixos/tests/kubo/kubo-fuse.nix b/nixos/tests/kubo/kubo-fuse.nix
new file mode 100644
index 0000000000000..71a5bf61649f6
--- /dev/null
+++ b/nixos/tests/kubo/kubo-fuse.nix
@@ -0,0 +1,42 @@
+{ lib, ...} : {
+  name = "kubo-fuse";
+  meta = with lib.maintainers; {
+    maintainers = [ mguentner Luflosi ];
+  };
+
+  nodes.machine = { config, ... }: {
+    services.kubo = {
+      enable = true;
+      autoMount = true;
+    };
+    users.users.alice = {
+      isNormalUser = true;
+      extraGroups = [ config.services.kubo.group ];
+    };
+    users.users.bob = {
+      isNormalUser = true;
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("FUSE mountpoint"):
+        machine.fail("echo a | su bob -l -c 'ipfs add --quieter'")
+        # The FUSE mount functionality is broken as of v0.13.0 and v0.17.0.
+        # See https://github.com/ipfs/kubo/issues/9044.
+        # Workaround: using CID Version 1 avoids that.
+        ipfs_hash = machine.succeed(
+            "echo fnord3 | su alice -l -c 'ipfs add --quieter --cid-version=1'"
+        ).strip()
+
+        machine.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")
+
+    with subtest("Unmounting of /ipns and /ipfs"):
+        # Force Kubo to crash and wait for it to restart
+        machine.systemctl("kill --signal=SIGKILL ipfs.service")
+        machine.wait_for_unit("ipfs.service", timeout = 30)
+
+        machine.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")
+  '';
+}
diff --git a/nixos/tests/kubo.nix b/nixos/tests/kubo/kubo.nix
index 496f409a40a93..b8222c652b33c 100644
--- a/nixos/tests/kubo.nix
+++ b/nixos/tests/kubo/kubo.nix
@@ -18,20 +18,6 @@
     };
   };
 
-  nodes.fuse = { config, ... }: {
-    services.kubo = {
-      enable = true;
-      autoMount = true;
-    };
-    users.users.alice = {
-      isNormalUser = true;
-      extraGroups = [ config.services.kubo.group ];
-    };
-    users.users.bob = {
-      isNormalUser = true;
-    };
-  };
-
   testScript = ''
     start_all()
 
@@ -60,26 +46,15 @@
             f"ipfs --api /unix/run/ipfs.sock cat /ipfs/{ipfs_hash.strip()} | grep fnord2"
         )
 
+    machine.stop_job("ipfs")
+
+    with subtest("Socket activation for the Gateway"):
+        machine.succeed(
+            f"curl 'http://127.0.0.1:8080/ipfs/{ipfs_hash.strip()}' | grep fnord2"
+        )
+
     with subtest("Setting dataDir works properly with the hardened systemd unit"):
         machine.succeed("test -e /mnt/ipfs/config")
         machine.succeed("test ! -e /var/lib/ipfs/")
-
-    with subtest("FUSE mountpoint"):
-        fuse.fail("echo a | su bob -l -c 'ipfs add --quieter'")
-        # The FUSE mount functionality is broken as of v0.13.0 and v0.17.0.
-        # See https://github.com/ipfs/kubo/issues/9044.
-        # Workaround: using CID Version 1 avoids that.
-        ipfs_hash = fuse.succeed(
-            "echo fnord3 | su alice -l -c 'ipfs add --quieter --cid-version=1'"
-        ).strip()
-
-        fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")
-
-    with subtest("Unmounting of /ipns and /ipfs"):
-        # Force Kubo to crash and wait for it to restart
-        fuse.systemctl("kill --signal=SIGKILL ipfs.service")
-        fuse.wait_for_unit("ipfs.service", timeout = 30)
-
-        fuse.succeed(f"cat /ipfs/{ipfs_hash} | grep fnord3")
   '';
 }
diff --git a/nixos/tests/lanraragi.nix b/nixos/tests/lanraragi.nix
new file mode 100644
index 0000000000000..7a4a1a489bdff
--- /dev/null
+++ b/nixos/tests/lanraragi.nix
@@ -0,0 +1,38 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "lanraragi";
+  meta.maintainers = with lib.maintainers; [ tomasajt ];
+
+  nodes = {
+    machine1 = { pkgs, ... }: {
+      services.lanraragi.enable = true;
+    };
+    machine2 = { pkgs, ... }: {
+      services.lanraragi = {
+        enable = true;
+        passwordFile = pkgs.writeText "lrr-test-pass" ''
+          Ultra-secure-p@ssword-"with-spec1al\chars
+        '';
+        port = 4000;
+        redis = {
+          port = 4001;
+          passwordFile = pkgs.writeText "redis-lrr-test-pass" ''
+            123-redis-PASS
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine1.wait_for_unit("lanraragi.service")
+    machine1.wait_until_succeeds("curl -f localhost:3000")
+    machine1.succeed("[ $(curl -o /dev/null -X post 'http://localhost:3000/login' --data-raw 'password=kamimamita' -w '%{http_code}') -eq 302 ]")
+
+    machine2.wait_for_unit("lanraragi.service")
+    machine2.wait_until_succeeds("curl -f localhost:4000")
+    machine2.succeed("[ $(curl -o /dev/null -X post 'http://localhost:4000/login' --data-raw 'password=Ultra-secure-p@ssword-\"with-spec1al\\chars' -w '%{http_code}') -eq 302 ]")
+  '';
+})
+
diff --git a/nixos/tests/legit.nix b/nixos/tests/legit.nix
index 3eb3f50356998..a71fb1743c76f 100644
--- a/nixos/tests/legit.nix
+++ b/nixos/tests/legit.nix
@@ -8,7 +8,7 @@ in
   meta.maintainers = [ lib.maintainers.ratsclub ];
 
   nodes = {
-    server = { config, pkgs }: {
+    server = { config, pkgs, ... }: {
       services.legit = {
         enable = true;
         settings = {
diff --git a/nixos/tests/lemmy.nix b/nixos/tests/lemmy.nix
index de2c4938fe231..e8d747f89a9e7 100644
--- a/nixos/tests/lemmy.nix
+++ b/nixos/tests/lemmy.nix
@@ -59,6 +59,7 @@ in
         server.succeed("curl --fail localhost:${toString uiPort}")
 
     with subtest("Lemmy-UI responds through the caddy reverse proxy"):
+        server.systemctl("start network-online.target")
         server.wait_for_unit("network-online.target")
         server.wait_for_unit("caddy.service")
         server.wait_for_open_port(80)
@@ -66,6 +67,7 @@ in
         assert "Lemmy" in body, f"String Lemmy not found in response for ${lemmyNodeName}: \n{body}"
 
     with subtest("the server is exposed externally"):
+        client.systemctl("start network-online.target")
         client.wait_for_unit("network-online.target")
         client.succeed("curl -v --fail ${lemmyNodeName}")
 
diff --git a/nixos/tests/librenms.nix b/nixos/tests/librenms.nix
new file mode 100644
index 0000000000000..c59f56a323161
--- /dev/null
+++ b/nixos/tests/librenms.nix
@@ -0,0 +1,108 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  api_token = "f87f42114e44b63ad1b9e3c3d33d6fbe"; # random md5 hash
+  wrong_api_token = "e68ba041fcf1eab923a7a6de3af5f726"; # another random md5 hash
+in {
+  name = "librenms";
+  meta.maintainers = lib.teams.wdz.members;
+
+  nodes.librenms = {
+    time.timeZone = "Europe/Berlin";
+
+    environment.systemPackages = with pkgs; [
+      curl
+      jq
+    ];
+
+    services.librenms = {
+      enable = true;
+      hostname = "librenms";
+      database = {
+        createLocally = true;
+        host = "localhost";
+        database = "librenms";
+        username = "librenms";
+        passwordFile = pkgs.writeText "librenms-db-pass" "librenmsdbpass";
+      };
+      nginx = {
+        default = true;
+      };
+      enableOneMinutePolling = true;
+      settings = {
+        enable_billing = true;
+      };
+    };
+
+    # systemd oneshot to create a dummy admin user and a API token for testing
+    systemd.services.lnms-api-init = {
+      description = "LibreNMS API init";
+      after = [ "librenms-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        User = "root";
+        Group = "root";
+      };
+      script = ''
+        API_USER_NAME=api
+        API_TOKEN=${api_token} # random md5 hash
+
+        # we don't need to know the password, it just has to exist
+        API_USER_PASS=$(${pkgs.pwgen}/bin/pwgen -s 64 1)
+        ${pkgs.librenms}/artisan user:add $API_USER_NAME -r admin -p $API_USER_PASS
+        API_USER_ID=$(${pkgs.mariadb}/bin/mysql -D librenms -N -B -e "SELECT user_id FROM users WHERE username = '$API_USER_NAME';")
+
+        ${pkgs.mariadb}/bin/mysql -D librenms -e "INSERT INTO api_tokens (user_id, token_hash, description) VALUES ($API_USER_ID, '$API_TOKEN', 'API User')"
+      '';
+    };
+  };
+
+  nodes.snmphost = {
+    networking.firewall.allowedUDPPorts = [ 161 ];
+
+    systemd.services.snmpd = {
+      description = "snmpd";
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "forking";
+        User = "root";
+        Group = "root";
+        ExecStart = let
+          snmpd-config = pkgs.writeText "snmpd-config" ''
+            com2sec readonly default public
+
+            group MyROGroup v2c        readonly
+            view all    included  .1                               80
+            access MyROGroup ""      any       noauth    exact  all    none   none
+
+            syslocation Testcity, Testcountry
+            syscontact Testi mc Test <test@example.com>
+          '';
+        in "${pkgs.net-snmp}/bin/snmpd -c ${snmpd-config} -C";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    snmphost.wait_until_succeeds("pgrep snmpd")
+
+    librenms.wait_for_unit("lnms-api-init.service")
+    librenms.wait_for_open_port(80)
+
+    # Test that we can authenticate against the API
+    librenms.succeed("curl --fail -H 'X-Auth-Token: ${api_token}' http://localhost/api/v0")
+    librenms.fail("curl --fail -H 'X-Auth-Token: ${wrong_api_token}' http://localhost/api/v0")
+
+    # add snmphost as a device
+    librenms.succeed("curl --fail -X POST -d '{\"hostname\":\"snmphost\",\"version\":\"v2c\",\"community\":\"public\"}' -H 'X-Auth-Token: ${api_token}' http://localhost/api/v0/devices")
+
+    # wait until snmphost gets polled
+    librenms.wait_until_succeeds("test $(curl -H 'X-Auth-Token: ${api_token}' http://localhost/api/v0/devices/snmphost | jq -Mr .devices[0].last_polled) != 'null'")
+  '';
+})
diff --git a/nixos/tests/libvirtd.nix b/nixos/tests/libvirtd.nix
index 41d06cc9643fe..df80dcc21a2eb 100644
--- a/nixos/tests/libvirtd.nix
+++ b/nixos/tests/libvirtd.nix
@@ -14,10 +14,10 @@ import ./make-test-python.nix ({ pkgs, ... }: {
           libvirtd.hooks.qemu.is_working = "${pkgs.writeShellScript "testHook.sh" ''
             touch /tmp/qemu_hook_is_working
           ''}";
+          libvirtd.nss.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 ];
       };
diff --git a/nixos/tests/lighttpd.nix b/nixos/tests/lighttpd.nix
index 36e2745c55c15..daef1584a45c7 100644
--- a/nixos/tests/lighttpd.nix
+++ b/nixos/tests/lighttpd.nix
@@ -17,5 +17,6 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     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}'"
+    server.succeed("systemctl reload lighttpd")
   '';
 })
diff --git a/nixos/tests/litestream.nix b/nixos/tests/litestream.nix
index f9d71c526e9e7..a281d8538694c 100644
--- a/nixos/tests/litestream.nix
+++ b/nixos/tests/litestream.nix
@@ -44,14 +44,22 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       };
       services.grafana = {
         enable = true;
-        security = {
-          adminUser = "admin";
-          adminPassword = "admin";
-        };
-        addr = "localhost";
-        port = 3000;
-        extraOptions = {
-          DATABASE_URL = "sqlite3:///var/lib/grafana/data/grafana.db?cache=private&mode=rwc&_journal_mode=WAL";
+        settings = {
+          security = {
+            admin_user = "admin";
+            admin_password = "admin";
+          };
+
+          server = {
+            http_addr = "localhost";
+            http_port = 3000;
+          };
+
+          database = {
+            type = "sqlite3";
+            path = "/var/lib/grafana/data/grafana.db";
+            wal = true;
+          };
         };
       };
       users.users.foo = {
diff --git a/nixos/tests/livebook-service.nix b/nixos/tests/livebook-service.nix
new file mode 100644
index 0000000000000..56b4eb932f343
--- /dev/null
+++ b/nixos/tests/livebook-service.nix
@@ -0,0 +1,43 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "livebook-service";
+
+  nodes = {
+    machine = { config, pkgs, ... }: {
+      imports = [
+        ./common/user-account.nix
+      ];
+
+      services.livebook = {
+        enableUserService = true;
+        port = 20123;
+        environmentFile = pkgs.writeText "livebook.env" ''
+          LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+        '';
+        options = {
+          cookie = "chocolate chip";
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+      sudo = lib.concatStringsSep " " [
+        "XDG_RUNTIME_DIR=/run/user/${toString user.uid}"
+        "sudo"
+        "--preserve-env=XDG_RUNTIME_DIR"
+        "-u"
+        "alice"
+      ];
+    in
+    ''
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("loginctl enable-linger alice")
+      machine.wait_until_succeeds("${sudo} systemctl --user is-active livebook.service")
+      machine.wait_for_open_port(20123)
+
+      machine.succeed("curl -L localhost:20123 | grep 'Type password'")
+    '';
+})
diff --git a/nixos/tests/lvm2/systemd-stage-1.nix b/nixos/tests/lvm2/systemd-stage-1.nix
index b581f2b23507e..1c95aadfcb3f1 100644
--- a/nixos/tests/lvm2/systemd-stage-1.nix
+++ b/nixos/tests/lvm2/systemd-stage-1.nix
@@ -54,9 +54,9 @@
     '';
   }.${flavour};
 
-in import ../make-test-python.nix ({ pkgs, ... }: {
+in import ../make-test-python.nix ({ pkgs, lib, ... }: {
   name = "lvm2-${flavour}-systemd-stage-1";
-  meta.maintainers = with pkgs.lib.maintainers; [ das_j ];
+  meta.maintainers = lib.teams.helsinki-systems.members;
 
   nodes.machine = { pkgs, lib, ... }: {
     imports = [ extraConfig ];
diff --git a/nixos/tests/lvm2/thinpool.nix b/nixos/tests/lvm2/thinpool.nix
index 14781a8a60459..f49c8980613ce 100644
--- a/nixos/tests/lvm2/thinpool.nix
+++ b/nixos/tests/lvm2/thinpool.nix
@@ -1,7 +1,7 @@
 { kernelPackages ? null }:
 import ../make-test-python.nix ({ pkgs, lib, ... }: {
   name = "lvm2-thinpool";
-  meta.maintainers = with pkgs.lib.maintainers; [ ajs124 ];
+  meta.maintainers = lib.teams.helsinki-systems.members;
 
   nodes.machine = { pkgs, lib, ... }: {
     virtualisation.emptyDiskImages = [ 4096 ];
diff --git a/nixos/tests/lvm2/vdo.nix b/nixos/tests/lvm2/vdo.nix
index 5b014c2f72223..75c1fc094e97f 100644
--- a/nixos/tests/lvm2/vdo.nix
+++ b/nixos/tests/lvm2/vdo.nix
@@ -1,7 +1,7 @@
 { kernelPackages ? null }:
-import ../make-test-python.nix ({ pkgs, ... }: {
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
   name = "lvm2-vdo";
-  meta.maintainers = with pkgs.lib.maintainers; [ ajs124 ];
+  meta.maintainers = lib.teams.helsinki-systems.members;
 
   nodes.machine = { pkgs, lib, ... }: {
     # Minimum required size for VDO volume: 5063921664 bytes
diff --git a/nixos/tests/lxd-image-server.nix b/nixos/tests/lxd-image-server.nix
index d0afa495a5b1d..619542bdd9456 100644
--- a/nixos/tests/lxd-image-server.nix
+++ b/nixos/tests/lxd-image-server.nix
@@ -8,8 +8,8 @@ let
     };
   };
 
-  lxd-image-metadata = lxd-image.lxdMeta.${pkgs.stdenv.hostPlatform.system};
-  lxd-image-rootfs = lxd-image.lxdImage.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-metadata = lxd-image.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-rootfs = lxd-image.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
 
 in {
   name = "lxd-image-server";
diff --git a/nixos/tests/lxd/container.nix b/nixos/tests/lxd/container.nix
index a2b61b78f7d69..ef9c3f4bbee7e 100644
--- a/nixos/tests/lxd/container.nix
+++ b/nixos/tests/lxd/container.nix
@@ -13,17 +13,18 @@ let
 
   lxd-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
   lxd-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+  lxd-image-rootfs-squashfs = releases.lxdContainerImageSquashfs.${pkgs.stdenv.hostPlatform.system};
 
 in {
   name = "lxd-container";
 
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ patryk27 adamcstephens ];
+  meta = {
+    maintainers = lib.teams.lxc.members;
   };
 
   nodes.machine = { lib, ... }: {
     virtualisation = {
-      diskSize = 4096;
+      diskSize = 6144;
 
       # Since we're testing `limits.cpu`, we've gotta have a known number of
       # cores to lean on
@@ -49,6 +50,9 @@ in {
     # Wait for lxd to settle
     machine.succeed("lxd waitready")
 
+    # no preseed should mean no service
+    machine.fail("systemctl status lxd-preseed.service")
+
     machine.succeed("lxd init --minimal")
 
     machine.succeed(
@@ -62,6 +66,16 @@ in {
         machine.succeed("echo true | lxc exec container /run/current-system/sw/bin/bash -")
         machine.succeed("lxc delete -f container")
 
+    with subtest("Squashfs image is functional"):
+        machine.succeed(
+            "lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-rootfs-squashfs} --alias nixos-squashfs"
+        )
+        machine.succeed("lxc launch nixos-squashfs container")
+        with machine.nested("Waiting for instance to start and be usable"):
+          retry(instance_is_up)
+        machine.succeed("echo true | lxc exec container /run/current-system/sw/bin/bash -")
+        machine.succeed("lxc delete -f container")
+
     with subtest("Container is mounted with lxcfs inside"):
         machine.succeed("lxc launch nixos container")
         with machine.nested("Waiting for instance to start and be usable"):
diff --git a/nixos/tests/lxd/default.nix b/nixos/tests/lxd/default.nix
index 8ca591211a061..20afdd5e48bbe 100644
--- a/nixos/tests/lxd/default.nix
+++ b/nixos/tests/lxd/default.nix
@@ -2,9 +2,11 @@
   system ? builtins.currentSystem,
   config ? {},
   pkgs ? import ../../.. {inherit system config;},
+  handleTestOn,
 }: {
   container = import ./container.nix {inherit system pkgs;};
   nftables = import ./nftables.nix {inherit system pkgs;};
+  preseed = import ./preseed.nix {inherit system pkgs;};
   ui = import ./ui.nix {inherit system pkgs;};
-  virtual-machine = import ./virtual-machine.nix { inherit system pkgs; };
+  virtual-machine = handleTestOn ["x86_64-linux"] ./virtual-machine.nix { inherit system pkgs; };
 }
diff --git a/nixos/tests/lxd/nftables.nix b/nixos/tests/lxd/nftables.nix
index d98bd4952906b..e6ce4089d719d 100644
--- a/nixos/tests/lxd/nftables.nix
+++ b/nixos/tests/lxd/nftables.nix
@@ -5,11 +5,11 @@
 # iptables to nftables requires a full reboot, which is a bit hard inside NixOS
 # tests.
 
-import ../make-test-python.nix ({ pkgs, ...} : {
+import ../make-test-python.nix ({ pkgs, lib, ...} : {
   name = "lxd-nftables";
 
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ patryk27 ];
+  meta = {
+    maintainers = lib.teams.lxc.members;
   };
 
   nodes.machine = { lib, ... }: {
diff --git a/nixos/tests/lxd/preseed.nix b/nixos/tests/lxd/preseed.nix
new file mode 100644
index 0000000000000..fb80dcf3893e4
--- /dev/null
+++ b/nixos/tests/lxd/preseed.nix
@@ -0,0 +1,71 @@
+import ../make-test-python.nix ({ pkgs, lib, ... } :
+
+{
+  name = "lxd-preseed";
+
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  nodes.machine = { lib, ... }: {
+    virtualisation = {
+      diskSize = 4096;
+
+      lxc.lxcfs.enable = true;
+      lxd.enable = true;
+
+      lxd.preseed = {
+        networks = [
+          {
+            name = "nixostestbr0";
+            type = "bridge";
+            config = {
+              "ipv4.address" = "10.0.100.1/24";
+              "ipv4.nat" = "true";
+            };
+          }
+        ];
+        profiles = [
+          {
+            name = "nixostest_default";
+            devices = {
+              eth0 = {
+                name = "eth0";
+                network = "nixostestbr0";
+                type = "nic";
+              };
+              root = {
+                path = "/";
+                pool = "default";
+                size = "35GiB";
+                type = "disk";
+              };
+            };
+          }
+        ];
+        storage_pools = [
+          {
+            name = "nixostest_pool";
+            driver = "dir";
+          }
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    def wait_for_preseed(_) -> bool:
+      _, output = machine.systemctl("is-active lxd-preseed.service")
+      return ("inactive" in output)
+
+    machine.wait_for_unit("sockets.target")
+    machine.wait_for_unit("lxd.service")
+    with machine.nested("Waiting for preseed to complete"):
+      retry(wait_for_preseed)
+
+    with subtest("Verify preseed resources created"):
+      machine.succeed("lxc profile show nixostest_default")
+      machine.succeed("lxc network info nixostestbr0")
+      machine.succeed("lxc storage show nixostest_pool")
+  '';
+})
diff --git a/nixos/tests/lxd/ui.nix b/nixos/tests/lxd/ui.nix
index 86cb30d8c2b68..c442f44ab81cd 100644
--- a/nixos/tests/lxd/ui.nix
+++ b/nixos/tests/lxd/ui.nix
@@ -1,8 +1,8 @@
 import ../make-test-python.nix ({ pkgs, lib, ... }: {
   name = "lxd-ui";
 
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ jnsgruk ];
+  meta = {
+    maintainers = lib.teams.lxc.members;
   };
 
   nodes.machine = { lib, ... }: {
@@ -11,9 +11,37 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: {
       lxd.ui.enable = true;
     };
 
-    environment.systemPackages = [ pkgs.curl ];
+    environment.systemPackages =
+      let
+        seleniumScript = pkgs.writers.writePython3Bin "selenium-script"
+          {
+            libraries = with pkgs.python3Packages; [ selenium ];
+          } ''
+          from selenium import webdriver
+          from selenium.webdriver.common.by import By
+          from selenium.webdriver.firefox.options import Options
+          from selenium.webdriver.support.ui import WebDriverWait
+
+          options = Options()
+          options.add_argument("--headless")
+          service = webdriver.FirefoxService(executable_path="${lib.getExe pkgs.geckodriver}")  # noqa: E501
+
+          driver = webdriver.Firefox(options=options, service=service)
+          driver.implicitly_wait(10)
+          driver.get("https://localhost:8443/ui")
+
+          wait = WebDriverWait(driver, 60)
+
+          assert len(driver.find_elements(By.CLASS_NAME, "l-application")) > 0
+          assert len(driver.find_elements(By.CLASS_NAME, "l-navigation__drawer")) > 0
+
+          driver.close()
+        '';
+      in
+      with pkgs; [ curl firefox-unwrapped geckodriver seleniumScript ];
   };
 
+
   testScript = ''
     machine.wait_for_unit("sockets.target")
     machine.wait_for_unit("lxd.service")
@@ -31,5 +59,8 @@ import ../make-test-python.nix ({ pkgs, lib, ... }: {
 
     # Ensure the endpoint returns an HTML page with 'LXD UI' in the title
     machine.succeed("curl -kLs https://localhost:8443/ui | grep '<title>LXD UI</title>'")
+
+    # Ensure the application is actually rendered by the Javascript
+    machine.succeed("PYTHONUNBUFFERED=1 selenium-script")
   '';
 })
diff --git a/nixos/tests/lxd/virtual-machine.nix b/nixos/tests/lxd/virtual-machine.nix
index 93705e9350c5a..2a9dd8fcdbf61 100644
--- a/nixos/tests/lxd/virtual-machine.nix
+++ b/nixos/tests/lxd/virtual-machine.nix
@@ -18,8 +18,8 @@ let
 in {
   name = "lxd-virtual-machine";
 
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [adamcstephens];
+  meta = {
+    maintainers = lib.teams.lxc.members;
   };
 
   nodes.machine = {lib, ...}: {
diff --git a/nixos/tests/mailman.nix b/nixos/tests/mailman.nix
index 2806e9166d9ac..f9b43861a12f6 100644
--- a/nixos/tests/mailman.nix
+++ b/nixos/tests/mailman.nix
@@ -63,5 +63,11 @@ import ./make-test-python.nix {
         wait_for_api()
         machine.succeed("curl --fail-with-body -sLSu restadmin:secretpassword http://localhost:8001/3.1/domains")
         machine.succeed("curl --fail-with-body -sILS http://localhost/")
+
+    with subtest("service locking"):
+        machine.fail("su -s /bin/sh -c 'mailman start' mailman")
+        machine.execute("systemctl kill --signal=SIGKILL mailman")
+        machine.succeed("systemctl restart mailman")
+        wait_for_api()
   '';
 }
diff --git a/nixos/tests/mate.nix b/nixos/tests/mate.nix
index 78ba59c5fc20d..48582e18d520c 100644
--- a/nixos/tests/mate.nix
+++ b/nixos/tests/mate.nix
@@ -27,9 +27,12 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     hardware.pulseaudio.enable = true;
   };
 
+  enableOCR = true;
+
   testScript = { nodes, ... }:
     let
       user = nodes.machine.users.users.alice;
+      env = "DISPLAY=:0.0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
     in
     ''
       with subtest("Wait for login"):
@@ -48,11 +51,31 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           machine.wait_for_window("Bottom Panel")
           machine.wait_until_succeeds("pgrep caja")
           machine.wait_for_window("Caja")
+          machine.wait_for_text('(Applications|Places|System)')
+          machine.wait_for_text('(Computer|Home|Trash)')
+
+      with subtest("Lock the screen"):
+          machine.wait_until_succeeds("su - ${user.name} -c '${env} mate-screensaver-command -q' | grep 'The screensaver is inactive'")
+          machine.succeed("su - ${user.name} -c '${env} mate-screensaver-command -l >&2 &'")
+          machine.wait_until_succeeds("su - ${user.name} -c '${env} mate-screensaver-command -q' | grep 'The screensaver is active'")
+          machine.sleep(2)
+          machine.send_chars("${user.password}", delay=0.2)
+          machine.wait_for_text("${user.description}")
+          machine.screenshot("screensaver")
+          machine.send_chars("\n")
+          machine.wait_until_succeeds("su - ${user.name} -c '${env} mate-screensaver-command -q' | grep 'The screensaver is inactive'")
+
+      with subtest("Open MATE control center"):
+          machine.succeed("su - ${user.name} -c '${env} mate-control-center >&2 &'")
+          machine.wait_for_window("Control Center")
+          machine.wait_for_text('(Groups|Administration|Hardware)')
 
       with subtest("Open MATE terminal"):
-          machine.succeed("su - ${user.name} -c 'DISPLAY=:0.0 mate-terminal >&2 &'")
+          machine.succeed("su - ${user.name} -c '${env} mate-terminal >&2 &'")
           machine.wait_for_window("Terminal")
-          machine.sleep(20)
+
+      with subtest("Check if MATE has ever coredumped"):
+          machine.fail("coredumpctl --json=short | grep -E 'mate|marco|caja'")
           machine.screenshot("screen")
     '';
 })
diff --git a/nixos/tests/matrix/synapse-workers.nix b/nixos/tests/matrix/synapse-workers.nix
new file mode 100644
index 0000000000000..e90301aeae9e4
--- /dev/null
+++ b/nixos/tests/matrix/synapse-workers.nix
@@ -0,0 +1,50 @@
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "matrix-synapse-workers";
+  meta = with pkgs.lib; {
+    maintainers = teams.matrix.members;
+  };
+
+  nodes = {
+    homeserver =
+      { pkgs
+      , nodes
+      , ...
+      }: {
+        services.postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "synapse-init.sql" ''
+            CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+            CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+            TEMPLATE template0
+            LC_COLLATE = "C"
+            LC_CTYPE = "C";
+          '';
+        };
+
+        services.matrix-synapse = {
+          enable = true;
+          settings = {
+            database = {
+              name = "psycopg2";
+              args.password = "synapse";
+            };
+            enable_registration = true;
+            enable_registration_without_verification = true;
+
+            federation_sender_instances = [ "federation_sender" ];
+          };
+          configureRedisLocally = true;
+          workers = {
+            "federation_sender" = { };
+          };
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+
+    homeserver.wait_for_unit("matrix-synapse.service");
+    homeserver.wait_for_unit("matrix-synapse-worker-federation_sender.service");
+  '';
+})
diff --git a/nixos/tests/matrix/synapse.nix b/nixos/tests/matrix/synapse.nix
index 98b0774691923..8c10a575ffbd3 100644
--- a/nixos/tests/matrix/synapse.nix
+++ b/nixos/tests/matrix/synapse.nix
@@ -1,31 +1,15 @@
 import ../make-test-python.nix ({ pkgs, ... } : let
 
-
-  runWithOpenSSL = file: cmd: pkgs.runCommand file {
-    buildInputs = [ pkgs.openssl ];
-  } cmd;
-
-
-  ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
-  ca_pem = runWithOpenSSL "ca.pem" ''
-    openssl req \
-      -x509 -new -nodes -key ${ca_key} \
-      -days 10000 -out $out -subj "/CN=snakeoil-ca"
+  ca_key = mailerCerts.ca.key;
+  ca_pem = mailerCerts.ca.cert;
+
+  bundle = pkgs.runCommand "bundle" {
+    nativeBuildInputs = [ pkgs.minica ];
+  } ''
+    minica -ca-cert ${ca_pem} -ca-key ${ca_key} \
+      -domains localhost
+    install -Dm444 -t $out localhost/{key,cert}.pem
   '';
-  key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
-  csr = runWithOpenSSL "matrix.csr" ''
-    openssl req \
-       -new -key ${key} \
-       -out $out -subj "/CN=localhost" \
-  '';
-  cert = runWithOpenSSL "matrix_cert.pem" ''
-    openssl x509 \
-      -req -in ${csr} \
-      -CA ${ca_pem} -CAkey ${ca_key} \
-      -CAcreateserial -out $out \
-      -days 365
-  '';
-
 
   mailerCerts = import ../common/acme/server/snakeoil-certs.nix;
   mailerDomain = mailerCerts.domain;
@@ -82,8 +66,8 @@ in {
             host = "localhost";
             port = config.services.redis.servers.matrix-synapse.port;
           };
-          tls_certificate_path = "${cert}";
-          tls_private_key_path = "${key}";
+          tls_certificate_path = "${bundle}/cert.pem";
+          tls_private_key_path = "${bundle}/key.pem";
           registration_shared_secret = registrationSharedSecret;
           public_baseurl = "https://example.com";
           email = {
@@ -203,8 +187,8 @@ in {
         settings = {
           inherit listeners;
           database.name = "sqlite3";
-          tls_certificate_path = "${cert}";
-          tls_private_key_path = "${key}";
+          tls_certificate_path = "${bundle}/cert.pem";
+          tls_private_key_path = "${bundle}/key.pem";
         };
       };
     };
@@ -222,7 +206,7 @@ in {
         "journalctl -u matrix-synapse.service | grep -q 'Connected to redis'"
     )
     serverpostgres.require_unit_state("postgresql.service")
-    serverpostgres.succeed("register_new_matrix_user -u ${testUser} -p ${testPassword} -a -k ${registrationSharedSecret} https://localhost:8448/")
+    serverpostgres.succeed("REQUESTS_CA_BUNDLE=${ca_pem} 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/mediawiki.nix b/nixos/tests/mediawiki.nix
index 52122755ad947..e30cc55ff6160 100644
--- a/nixos/tests/mediawiki.nix
+++ b/nixos/tests/mediawiki.nix
@@ -74,4 +74,20 @@ in
       assert "MediaWiki has been installed" in page, f"no 'MediaWiki has been installed' in:\n{page}"
     '';
   };
+
+  nginx = testLib.makeTest {
+    name = "mediawiki-nginx";
+    nodes.machine = {
+      services.mediawiki.webserver = "nginx";
+    };
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("phpfpm-mediawiki.service")
+      machine.wait_for_unit("nginx.service")
+
+      page = machine.succeed("curl -fL http://localhost/")
+      assert "MediaWiki has been installed" in page
+    '';
+  };
 }
diff --git a/nixos/tests/miriway.nix b/nixos/tests/miriway.nix
index f12c4d5ecc41e..a0987d9fc41b6 100644
--- a/nixos/tests/miriway.nix
+++ b/nixos/tests/miriway.nix
@@ -31,7 +31,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         enable-x11=
 
         ctrl-alt=t:foot --maximized
-        ctrl-alt=a:env WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY=invalid alacritty --option window.startup_mode=maximized
+        ctrl-alt=a:env WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= alacritty --option window.startup_mode=\"maximized\"
 
         shell-component=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY
 
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index 442b45948c608..e7842debba7a2 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -13,6 +13,7 @@ in {
       environment.variables.EDITOR = lib.mkOverride 0 "emacs";
       documentation.nixos.enable = lib.mkOverride 0 true;
       systemd.tmpfiles.rules = [ "d /tmp 1777 root root 10d" ];
+      systemd.tmpfiles.settings."10-test"."/tmp/somefile".d = {};
       virtualisation.fileSystems = { "/tmp2" =
         { fsType = "tmpfs";
           options = [ "mode=1777" "noauto" ];
@@ -117,6 +118,9 @@ in {
           )
           machine.fail("[ -e /tmp/foo ]")
 
+      with subtest("whether systemd-tmpfiles settings works"):
+          machine.succeed("[ -e /tmp/somefile ]")
+
       with subtest("whether automounting works"):
           machine.fail("grep '/tmp2 tmpfs' /proc/mounts")
           machine.succeed("touch /tmp2/x")
diff --git a/nixos/tests/mobilizon.nix b/nixos/tests/mobilizon.nix
new file mode 100644
index 0000000000000..2b070ca9d9609
--- /dev/null
+++ b/nixos/tests/mobilizon.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ lib, ... }:
+  let
+    certs = import ./common/acme/server/snakeoil-certs.nix;
+    mobilizonDomain = certs.domain;
+    port = 41395;
+  in
+
+  {
+    name = "mobilizon";
+    meta.maintainers = with lib.maintainers; [ minijackson erictapen ];
+
+    nodes.server =
+      { ... }:
+      {
+        services.mobilizon = {
+          enable = true;
+          settings = {
+            ":mobilizon" = {
+              ":instance" = {
+                name = "Test Mobilizon";
+                hostname = mobilizonDomain;
+              };
+              "Mobilizon.Web.Endpoint".http.port = port;
+            };
+          };
+        };
+
+        security.pki.certificateFiles = [ certs.ca.cert ];
+
+        services.nginx.virtualHosts."${mobilizonDomain}" = {
+          enableACME = lib.mkForce false;
+          sslCertificate = certs.${mobilizonDomain}.cert;
+          sslCertificateKey = certs.${mobilizonDomain}.key;
+        };
+
+        networking.hosts."::1" = [ mobilizonDomain ];
+      };
+
+    testScript = ''
+      server.wait_for_unit("mobilizon.service")
+      server.wait_for_open_port(${toString port})
+      server.succeed("curl --fail https://${mobilizonDomain}/")
+    '';
+  })
diff --git a/nixos/tests/mongodb.nix b/nixos/tests/mongodb.nix
index 75b0c4c2ab2b1..68be6926865ec 100644
--- a/nixos/tests/mongodb.nix
+++ b/nixos/tests/mongodb.nix
@@ -27,7 +27,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
   in {
     name = "mongodb";
     meta = with pkgs.lib.maintainers; {
-      maintainers = [ bluescreen303 offline cstrahan rvl phile314 ];
+      maintainers = [ bluescreen303 offline phile314 ];
     };
 
     nodes = {
diff --git a/nixos/tests/mosquitto.nix b/nixos/tests/mosquitto.nix
index 8eca4f2592251..c0980b23e78fd 100644
--- a/nixos/tests/mosquitto.nix
+++ b/nixos/tests/mosquitto.nix
@@ -4,7 +4,6 @@ let
   port = 1888;
   tlsPort = 1889;
   anonPort = 1890;
-  bindTestPort = 18910;
   password = "VERY_secret";
   hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw==";
   topic = "test/foo";
@@ -127,10 +126,6 @@ in {
               };
             };
           }
-          {
-            settings.bind_interface = "eth0";
-            port = bindTestPort;
-          }
         ];
       };
     };
@@ -140,8 +135,6 @@ in {
   };
 
   testScript = ''
-    import json
-
     def mosquitto_cmd(binary, user, topic, port):
         return (
             "mosquitto_{} "
@@ -174,27 +167,6 @@ in {
     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"))
diff --git a/nixos/tests/munin.nix b/nixos/tests/munin.nix
index 4ec17e0339df0..e371b2dffa6b8 100644
--- a/nixos/tests/munin.nix
+++ b/nixos/tests/munin.nix
@@ -37,8 +37,10 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     with subtest("ensure munin-node starts and listens on 4949"):
         one.wait_for_unit("munin-node.service")
         one.wait_for_open_port(4949)
+
     with subtest("ensure munin-cron output is correct"):
         one.wait_for_file("/var/lib/munin/one/one-uptime-uptime-g.rrd")
         one.wait_for_file("/var/www/munin/one/index.html")
+        one.wait_for_file("/var/www/munin/one/one/diskstat_iops_vda-day.png", timeout=60)
   '';
 })
diff --git a/nixos/tests/musescore.nix b/nixos/tests/musescore.nix
index 6aeb0558a49db..0720631ed284b 100644
--- a/nixos/tests/musescore.nix
+++ b/nixos/tests/musescore.nix
@@ -63,14 +63,11 @@ in
 
     machine.send_key("tab")
     machine.send_key("tab")
-    machine.send_key("tab")
-    machine.send_key("tab")
-    machine.send_key("right")
-    machine.send_key("right")
     machine.send_key("ret")
 
-    machine.sleep(1)
+    machine.sleep(2)
 
+    machine.send_key("tab")
     # Type the beginning of https://de.wikipedia.org/wiki/Alle_meine_Entchen
     machine.send_chars("cdef6gg5aaaa7g")
     machine.sleep(1)
diff --git a/nixos/tests/mympd.nix b/nixos/tests/mympd.nix
new file mode 100644
index 0000000000000..ac6a896966e6b
--- /dev/null
+++ b/nixos/tests/mympd.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({pkgs, lib, ... }: {
+  name = "mympd";
+
+  nodes.mympd = {
+    services.mympd = {
+      enable = true;
+      settings = {
+        http_port = 8081;
+      };
+    };
+
+    services.mpd.enable = true;
+  };
+
+  testScript = ''
+    start_all();
+    machine.wait_for_unit("mympd.service");
+
+    # Ensure that mympd can connect to mpd
+    machine.wait_until_succeeds(
+      "journalctl -eu mympd -o cat | grep 'Connected to MPD'"
+    )
+
+    # Ensure that the web server is working
+    machine.succeed("curl http://localhost:8081 --compressed | grep -o myMPD")
+  '';
+})
diff --git a/nixos/tests/mysql/common.nix b/nixos/tests/mysql/common.nix
index 7fdf0f33d3f37..1cf52347f4c74 100644
--- a/nixos/tests/mysql/common.nix
+++ b/nixos/tests/mysql/common.nix
@@ -3,5 +3,8 @@
   mysqlPackages = {
     inherit (pkgs) mysql80;
   };
+  perconaPackages = {
+    inherit (pkgs) percona-server_8_0;
+  };
   mkTestName = pkg: "mariadb_${builtins.replaceStrings ["."] [""] (lib.versions.majorMinor pkg.version)}";
 }
diff --git a/nixos/tests/mysql/mariadb-galera.nix b/nixos/tests/mysql/mariadb-galera.nix
index c9962f49c02fd..7455abbce5fb0 100644
--- a/nixos/tests/mysql/mariadb-galera.nix
+++ b/nixos/tests/mysql/mariadb-galera.nix
@@ -17,8 +17,8 @@ let
     galeraPackage ? pkgs.mariadb-galera
   }: makeTest {
     name = "${name}-galera-mariabackup";
-    meta = with pkgs.lib.maintainers; {
-      maintainers = [ izorkin ajs124 das_j ];
+    meta = {
+      maintainers = with lib.maintainers; [ izorkin ] ++ lib.teams.helsinki-systems.members;
     };
 
     # The test creates a Galera cluster with 3 nodes and is checking if mariabackup-based SST works. The cluster is tested by creating a DB and an empty table on one node,
diff --git a/nixos/tests/mysql/mysql-backup.nix b/nixos/tests/mysql/mysql-backup.nix
index 968f56dd3c9bd..451f5c04ce467 100644
--- a/nixos/tests/mysql/mysql-backup.nix
+++ b/nixos/tests/mysql/mysql-backup.nix
@@ -15,9 +15,6 @@ let
     name ? mkTestName package
   }: makeTest {
     name = "${name}-backup";
-    meta = with pkgs.lib.maintainers; {
-      maintainers = [ rvl ];
-    };
 
     nodes = {
       master = { pkgs, ... }: {
diff --git a/nixos/tests/mysql/mysql-replication.nix b/nixos/tests/mysql/mysql-replication.nix
index 8f1695eb97e27..83da1e7b6cb88 100644
--- a/nixos/tests/mysql/mysql-replication.nix
+++ b/nixos/tests/mysql/mysql-replication.nix
@@ -18,8 +18,8 @@ let
     name ? mkTestName package,
   }: makeTest {
     name = "${name}-replication";
-    meta = with pkgs.lib.maintainers; {
-      maintainers = [ ajs124 das_j ];
+    meta = {
+      maintainers = lib.teams.helsinki-systems.members;
     };
 
     nodes = {
diff --git a/nixos/tests/mysql/mysql.nix b/nixos/tests/mysql/mysql.nix
index 6ddc49f86f7c0..0a61f9d38fe2e 100644
--- a/nixos/tests/mysql/mysql.nix
+++ b/nixos/tests/mysql/mysql.nix
@@ -6,7 +6,7 @@
 }:
 
 let
-  inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages mysqlPackages;
+  inherit (import ./common.nix { inherit pkgs lib; }) mkTestName mariadbPackages mysqlPackages perconaPackages;
 
   makeTest = import ./../make-test-python.nix;
   # Setup common users
@@ -18,8 +18,8 @@ let
     hasRocksDB ? pkgs.stdenv.hostPlatform.is64bit
   }: makeTest {
     inherit name;
-    meta = with lib.maintainers; {
-      maintainers = [ ajs124 das_j ];
+    meta = {
+      maintainers = lib.teams.helsinki-systems.members;
     };
 
     nodes = {
@@ -78,9 +78,6 @@ let
             };
           };
         };
-
-      mariadb =        {
-        };
     };
 
     testScript = ''
@@ -147,3 +144,8 @@ in
   // (lib.mapAttrs (_: package: makeMySQLTest {
     inherit package;
   }) mariadbPackages)
+  // (lib.mapAttrs (_: package: makeMySQLTest {
+    inherit package;
+    name = "percona_8_0";
+    hasMroonga = false; useSocketAuth = false;
+  }) perconaPackages)
diff --git a/nixos/tests/netdata.nix b/nixos/tests/netdata.nix
index c5f7294f79abc..e3438f63404e7 100644
--- a/nixos/tests/netdata.nix
+++ b/nixos/tests/netdata.nix
@@ -30,8 +30,8 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     # check if netdata can read disk ops for root owned processes.
     # if > 0, successful. verifies both netdata working and
     # apps.plugin has elevated capabilities.
-    url = "http://localhost:19999/api/v1/data\?chart=users.pwrites"
-    filter = '[.data[range(10)][.labels | indices("root")[0]]] | add | . > 0'
+    url = "http://localhost:19999/api/v1/data\?chart=user.root_disk_physical_io"
+    filter = '[.data[range(10)][2]] | add | . < 0'
     cmd = f"curl -s {url} | jq -e '{filter}'"
     netdata.wait_until_succeeds(cmd)
 
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 46fc715d0891d..6bd89902eedb3 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -113,8 +113,8 @@ let
         networking = {
           useNetworkd = networkd;
           useDHCP = false;
-          defaultGateway = "192.168.1.1";
-          defaultGateway6 = "fd00:1234:5678:1::1";
+          defaultGateway = { address = "192.168.1.1"; interface = "enp1s0"; };
+          defaultGateway6 = { address = "fd00:1234:5678:1::1"; interface = "enp1s0"; };
           interfaces.enp1s0.ipv4.addresses = [
             { address = "192.168.1.2"; prefixLength = 24; }
             { address = "192.168.1.3"; prefixLength = 32; }
@@ -130,6 +130,7 @@ let
           start_all()
 
           client.wait_for_unit("network.target")
+          router.systemctl("start network-online.target")
           router.wait_for_unit("network-online.target")
 
           with subtest("Make sure DHCP server is not started"):
@@ -185,7 +186,11 @@ let
       nodes.router = router;
       nodes.client = { lib, ... }: {
         # Disable test driver default config
-        networking.interfaces = lib.mkForce {};
+        networking.interfaces = lib.mkForce {
+          # Make sure DHCP defaults correctly even when some unrelated config
+          # is set on the interface (nothing, in this case).
+          enp1s0 = {};
+        };
         networking.useNetworkd = networkd;
         virtualisation.interfaces.enp1s0.vlan = 1;
       };
@@ -218,6 +223,7 @@ let
           start_all()
 
           client.wait_for_unit("network.target")
+          router.systemctl("start network-online.target")
           router.wait_for_unit("network-online.target")
 
           with subtest("Wait until we have an ip address on each interface"):
@@ -845,6 +851,7 @@ let
 
           client.wait_for_unit("network.target")
           client_with_privacy.wait_for_unit("network.target")
+          router.systemctl("start network-online.target")
           router.wait_for_unit("network-online.target")
 
           with subtest("Wait until we have an ip address"):
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
index b7af6d6d73647..428fe0aa10db9 100644
--- a/nixos/tests/nextcloud/basic.nix
+++ b/nixos/tests/nextcloud/basic.nix
@@ -13,10 +13,12 @@ in {
     # The only thing the client needs to do is download a file.
     client = { ... }: {
       services.davfs2.enable = true;
-      system.activationScripts.davfs2-secrets = ''
-        echo "http://nextcloud/remote.php/dav/files/${adminuser} ${adminuser} ${adminpass}" > /tmp/davfs2-secrets
-        chmod 600 /tmp/davfs2-secrets
-      '';
+      systemd.tmpfiles.settings.nextcloud = {
+        "/tmp/davfs2-secrets"."f+" = {
+          mode = "0600";
+          argument = "http://nextcloud/remote.php/dav/files/${adminuser} ${adminuser} ${adminpass}";
+        };
+      };
       virtualisation.fileSystems = {
         "/mnt/dav" = {
           device = "http://nextcloud/remote.php/dav/files/${adminuser}";
@@ -37,8 +39,6 @@ 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";
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
index b9f35b398cfec..84ac371537271 100644
--- a/nixos/tests/nextcloud/default.nix
+++ b/nixos/tests/nextcloud/default.nix
@@ -8,10 +8,6 @@ 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;
@@ -26,4 +22,4 @@ foldl
     };
   })
 { }
-  [ 25 26 27 ]
+  [ 26 27 28 ]
diff --git a/nixos/tests/nextcloud/openssl-sse.nix b/nixos/tests/nextcloud/openssl-sse.nix
deleted file mode 100644
index d6ea39c6155a5..0000000000000
--- a/nixos/tests/nextcloud/openssl-sse.nix
+++ /dev/null
@@ -1,109 +0,0 @@
-args@{ pkgs, nextcloudVersion ? 25, ... }:
-
-(import ../make-test-python.nix ({ pkgs, ...}: let
-  adminuser = "root";
-  adminpass = "notproduction";
-  nextcloudBase = {
-    networking.firewall.allowedTCPPorts = [ 80 ];
-    system.stateVersion = "22.05"; # stateVersions <22.11 use openssl 1.1 by default
-    services.nextcloud = {
-      enable = true;
-      config.adminpassFile = "${pkgs.writeText "adminpass" adminpass}";
-      database.createLocally = true;
-      package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
-    };
-  };
-in {
-  name = "nextcloud-openssl";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ ma27 ];
-  };
-  nodes.nextcloudwithopenssl1 = {
-    imports = [ nextcloudBase ];
-    services.nextcloud.hostName = "nextcloudwithopenssl1";
-  };
-  nodes.nextcloudwithopenssl3 = {
-    imports = [ nextcloudBase ];
-    services.nextcloud = {
-      hostName = "nextcloudwithopenssl3";
-      enableBrokenCiphersForSSE = false;
-    };
-  };
-  testScript = { nodes, ... }: let
-    withRcloneEnv = host: pkgs.writeScript "with-rclone-env" ''
-      #!${pkgs.runtimeShell}
-      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
-      export RCLONE_CONFIG_NEXTCLOUD_URL="http://${host}/remote.php/dav/files/${adminuser}"
-      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.system.build.toplevel;
-    openssl3-node = nodes.nextcloudwithopenssl3.system.build.toplevel;
-  in ''
-    nextcloudwithopenssl1.start()
-    nextcloudwithopenssl1.wait_for_unit("multi-user.target")
-    nextcloudwithopenssl1.succeed("nextcloud-occ status")
-    nextcloudwithopenssl1.succeed("curl -sSf http://nextcloudwithopenssl1/login")
-    nextcloud_version = ${toString nextcloudVersion}
-
-    with subtest("With OpenSSL 1 SSE can be enabled and used"):
-        nextcloudwithopenssl1.succeed("nextcloud-occ app:enable encryption")
-        nextcloudwithopenssl1.succeed("nextcloud-occ encryption:enable")
-
-    with subtest("Upload file and ensure it's encrypted"):
-        nextcloudwithopenssl1.succeed("${copySharedFile1}")
-        nextcloudwithopenssl1.succeed("grep -E '^HBEGIN:oc_encryption_module' /var/lib/nextcloud/data/root/files/test-shared-file")
-        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file | grep hi")
-
-    with subtest("Switch to OpenSSL 3"):
-        nextcloudwithopenssl1.succeed("${openssl3-node}/bin/switch-to-configuration test")
-        nextcloudwithopenssl1.wait_for_open_port(80)
-        nextcloudwithopenssl1.succeed("nextcloud-occ status")
-
-    with subtest("Existing encrypted files cannot be read, but new files can be added"):
-        # This will succeed starting NC26 because of their custom implementation of openssl_seal
-        read_existing_file_test = nextcloudwithopenssl1.fail if nextcloud_version < 26 else nextcloudwithopenssl1.succeed
-        read_existing_file_test("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file >&2")
-        nextcloudwithopenssl1.succeed("nextcloud-occ encryption:disable")
-        nextcloudwithopenssl1.succeed("${copySharedFile3}")
-        nextcloudwithopenssl1.succeed("grep bye /var/lib/nextcloud/data/root/files/test-shared-file2")
-        nextcloudwithopenssl1.succeed("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file2 | grep bye")
-
-    with subtest("Switch back to OpenSSL 1.1 and ensure that encrypted files are readable again"):
-        nextcloudwithopenssl1.succeed("${openssl1-node}/bin/switch-to-configuration test")
-        nextcloudwithopenssl1.wait_for_open_port(80)
-        nextcloudwithopenssl1.succeed("nextcloud-occ status")
-        nextcloudwithopenssl1.succeed("nextcloud-occ encryption:enable")
-        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file2 | grep bye")
-        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file | grep hi")
-        nextcloudwithopenssl1.succeed("grep -E '^HBEGIN:oc_encryption_module' /var/lib/nextcloud/data/root/files/test-shared-file")
-        nextcloudwithopenssl1.succeed("grep bye /var/lib/nextcloud/data/root/files/test-shared-file2")
-
-    with subtest("Ensure that everything can be decrypted"):
-        nextcloudwithopenssl1.succeed("echo y | nextcloud-occ encryption:decrypt-all >&2")
-        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file2 | grep bye")
-        nextcloudwithopenssl1.succeed("${withRcloneEnv1} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file | grep hi")
-        nextcloudwithopenssl1.succeed("grep -vE '^HBEGIN:oc_encryption_module' /var/lib/nextcloud/data/root/files/test-shared-file")
-
-    with subtest("Switch to OpenSSL 3 ensure that all files are usable now"):
-        nextcloudwithopenssl1.succeed("${openssl3-node}/bin/switch-to-configuration test")
-        nextcloudwithopenssl1.wait_for_open_port(80)
-        nextcloudwithopenssl1.succeed("nextcloud-occ status")
-        nextcloudwithopenssl1.succeed("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file2 | grep bye")
-        nextcloudwithopenssl1.succeed("${withRcloneEnv3} ${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file | grep hi")
-
-    nextcloudwithopenssl1.shutdown()
-  '';
-})) args
diff --git a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
index e638f2e5b861f..addc898bd7602 100644
--- a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
+++ b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
@@ -41,10 +41,13 @@ in {
         };
         secretFile = "/etc/nextcloud-secrets.json";
 
-        extraOptions.redis = {
-          dbindex = 0;
-          timeout = 1.5;
-          # password handled via secretfile below
+        extraOptions = {
+          allow_local_remote_servers = true;
+          redis = {
+            dbindex = 0;
+            timeout = 1.5;
+            # password handled via secretfile below
+          };
         };
         configureRedis = true;
       };
@@ -62,6 +65,7 @@ in {
 
       services.postgresql = {
         enable = true;
+        package = pkgs.postgresql_14;
       };
       systemd.services.postgresql.postStart = pkgs.lib.mkAfter ''
         password=$(cat ${passFile})
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 586bf50fd939c..d95af8a89d07a 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -32,7 +32,6 @@ in {
           adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
             ${adminpass}
           '');
-          trustedProxies = [ "::1" ];
         };
         notify_push = {
           enable = true;
@@ -42,6 +41,7 @@ in {
         extraApps = {
           inherit (pkgs."nextcloud${lib.versions.major config.services.nextcloud.package.version}Packages".apps) notify_push;
         };
+        extraOptions.trusted_proxies = [ "::1" ];
       };
 
       services.redis.servers."nextcloud".enable = true;
diff --git a/nixos/tests/nfs/kerberos.nix b/nixos/tests/nfs/kerberos.nix
index a7d08bc628c62..5944b53319a0b 100644
--- a/nixos/tests/nfs/kerberos.nix
+++ b/nixos/tests/nfs/kerberos.nix
@@ -1,15 +1,17 @@
 import ../make-test-python.nix ({ pkgs, lib, ... }:
 
 let
-  krb5 =
-    { enable = true;
-      domain_realm."nfs.test"   = "NFS.TEST";
+  security.krb5 = {
+    enable = true;
+    settings = {
+      domain_realm."nfs.test" = "NFS.TEST";
       libdefaults.default_realm = "NFS.TEST";
-      realms."NFS.TEST" =
-        { admin_server = "server.nfs.test";
-          kdc = "server.nfs.test";
-        };
+      realms."NFS.TEST" = {
+        admin_server = "server.nfs.test";
+        kdc = "server.nfs.test";
+      };
     };
+  };
 
   hosts =
     ''
@@ -32,7 +34,7 @@ in
 
   nodes = {
     client = { lib, ... }:
-      { inherit krb5 users;
+      { inherit security users;
 
         networking.extraHosts = hosts;
         networking.domain = "nfs.test";
@@ -48,7 +50,7 @@ in
       };
 
     server = { lib, ...}:
-      { inherit krb5 users;
+      { inherit security users;
 
         networking.extraHosts = hosts;
         networking.domain = "nfs.test";
@@ -103,6 +105,7 @@ in
       server.wait_for_unit("rpc-gssd.service")
       server.wait_for_unit("rpc-svcgssd.service")
 
+      client.systemctl("start network-online.target")
       client.wait_for_unit("network-online.target")
 
       # add principals to client keytab
@@ -128,4 +131,6 @@ in
           expected = ["alice", "users"]
           assert ids == expected, f"ids incorrect: got {ids} expected {expected}"
     '';
+
+  meta.maintainers = [ lib.maintainers.dblsaiko ];
 })
diff --git a/nixos/tests/nginx-etag-compression.nix b/nixos/tests/nginx-etag-compression.nix
new file mode 100644
index 0000000000000..67493ae299841
--- /dev/null
+++ b/nixos/tests/nginx-etag-compression.nix
@@ -0,0 +1,45 @@
+import ./make-test-python.nix {
+  name = "nginx-etag-compression";
+
+  nodes.machine = { pkgs, lib, ... }: {
+    services.nginx = {
+      enable = true;
+      recommendedGzipSettings = true;
+      virtualHosts.default = {
+        root = pkgs.runCommandLocal "testdir" {} ''
+          mkdir "$out"
+          cat > "$out/index.html" <<EOF
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          Hello, world!
+          EOF
+          ${pkgs.gzip}/bin/gzip -k "$out/index.html"
+        '';
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    machine.wait_for_unit("nginx")
+    machine.wait_for_open_port(80)
+
+    etag_plain = machine.succeed("curl -s -w'%header{etag}' -o/dev/null -H 'Accept-encoding:' http://127.0.0.1/")
+    etag_gzip = machine.succeed("curl -s -w'%header{etag}' -o/dev/null -H 'Accept-encoding:gzip' http://127.0.0.1/")
+
+    with subtest("different representations have different etags"):
+      assert etag_plain != etag_gzip, f"etags should differ: {etag_plain} == {etag_gzip}"
+
+    with subtest("etag for uncompressed response is reproducible"):
+      etag_plain_repeat = machine.succeed("curl -s -w'%header{etag}' -o/dev/null -H 'Accept-encoding:' http://127.0.0.1/")
+      assert etag_plain == etag_plain_repeat, f"etags should be the same: {etag_plain} != {etag_plain_repeat}"
+
+    with subtest("etag for compressed response is reproducible"):
+      etag_gzip_repeat = machine.succeed("curl -s -w'%header{etag}' -o/dev/null -H 'Accept-encoding:gzip' http://127.0.0.1/")
+      assert etag_gzip == etag_gzip_repeat, f"etags should be the same: {etag_gzip} != {etag_gzip_repeat}"
+  '';
+}
diff --git a/nixos/tests/nginx-http3.nix b/nixos/tests/nginx-http3.nix
index fc9f31037f989..22f7f61f10ce6 100644
--- a/nixos/tests/nginx-http3.nix
+++ b/nixos/tests/nginx-http3.nix
@@ -1,97 +1,113 @@
-import ./make-test-python.nix ({lib, pkgs, ...}:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system 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;
-          http3_hq = false;
-          quic = true;
-          reuseport = true;
-          root = lib.mkForce (pkgs.runCommandLocal "testdir" {} ''
-            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)
-      ];
-    };
-  };
+builtins.listToAttrs (
+  builtins.map
+    (nginxPackage:
+      {
+        name = pkgs.lib.getName nginxPackage;
+        value = makeTest {
+          name = "nginx-http3-${pkgs.lib.getName nginxPackage}";
+          meta.maintainers = with pkgs.lib.maintainers; [ izorkin ];
 
-  testScript = ''
-    start_all()
+          nodes = {
+            server = { lib, pkgs, ... }: {
+              networking = {
+                interfaces.eth1 = {
+                  ipv4.addresses = [
+                    { address = "192.168.2.101"; prefixLength = 24; }
+                  ];
+                };
+                extraHosts = hosts;
+                firewall.allowedTCPPorts = [ 443 ];
+                firewall.allowedUDPPorts = [ 443 ];
+              };
 
-    server.wait_for_unit("nginx")
-    server.wait_for_open_port(443)
+              security.pki.certificates = [
+                (builtins.readFile ./common/acme/server/ca.cert.pem)
+              ];
 
-    # Check http connections
-    client.succeed("curl --verbose --http3-only https://acme.test | grep 'Hello World!'")
+              services.nginx = {
+                enable = true;
+                package = nginxPackage;
 
-    # Check downloadings
-    client.succeed("curl --verbose --http3-only https://acme.test/example.txt --output /tmp/example.txt")
-    client.succeed("cat /tmp/example.txt | grep 'Check http3 protocol.'")
+                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;
+                  http3_hq = false;
+                  quic = true;
+                  reuseport = true;
+                  root = lib.mkForce (pkgs.runCommandLocal "testdir" {} ''
+                    mkdir "$out"
+                    cat > "$out/index.html" <<EOF
+                    <html><body>Hello World!</body></html>
+                    EOF
+                    cat > "$out/example.txt" <<EOF
+                    Check http3 protocol.
+                    EOF
+                  '');
+                };
+              };
+            };
 
-    # Check header reading
-    client.succeed("curl --verbose --http3-only --head https://acme.test | grep 'content-type'")
-    client.succeed("curl --verbose --http3-only --head https://acme.test | grep 'HTTP/3 200'")
-    client.succeed("curl --verbose --http3-only --head https://acme.test/error | grep 'HTTP/3 404'")
+            client = { pkgs, ... }: {
+              environment.systemPackages = [ pkgs.curlHTTP3 ];
+              networking = {
+                interfaces.eth1 = {
+                  ipv4.addresses = [
+                    { address = "192.168.2.201"; prefixLength = 24; }
+                  ];
+                };
+                extraHosts = hosts;
+              };
 
-    # Check change User-Agent
-    client.succeed("curl --verbose --http3-only --user-agent 'Curl test 3.0' https://acme.test")
-    server.succeed("cat /var/log/nginx/access.log | grep 'Curl test 3.0'")
+              security.pki.certificates = [
+                (builtins.readFile ./common/acme/server/ca.cert.pem)
+              ];
+            };
+          };
 
-    server.shutdown()
-    client.shutdown()
-  '';
-})
+          testScript = ''
+            start_all()
+
+            server.wait_for_unit("nginx")
+            server.wait_for_open_port(443)
+
+            # Check http connections
+            client.succeed("curl --verbose --http3-only https://acme.test | grep 'Hello World!'")
+
+            # Check downloadings
+            client.succeed("curl --verbose --http3-only https://acme.test/example.txt --output /tmp/example.txt")
+            client.succeed("cat /tmp/example.txt | grep 'Check http3 protocol.'")
+
+            # Check header reading
+            client.succeed("curl --verbose --http3-only --head https://acme.test | grep 'content-type'")
+            client.succeed("curl --verbose --http3-only --head https://acme.test | grep 'HTTP/3 200'")
+            client.succeed("curl --verbose --http3-only --head https://acme.test/error | grep 'HTTP/3 404'")
+
+            # Check change User-Agent
+            client.succeed("curl --verbose --http3-only --user-agent 'Curl test 3.0' https://acme.test")
+            server.succeed("cat /var/log/nginx/access.log | grep 'Curl test 3.0'")
+
+            server.shutdown()
+            client.shutdown()
+          '';
+        };
+      }
+    )
+    [ pkgs.angieQuic pkgs.nginxQuic ]
+)
diff --git a/nixos/tests/nginx-moreheaders.nix b/nixos/tests/nginx-moreheaders.nix
new file mode 100644
index 0000000000000..560dcf9ce0b82
--- /dev/null
+++ b/nixos/tests/nginx-moreheaders.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix {
+  name = "nginx-more-headers";
+
+  nodes = {
+    webserver = { pkgs, ... }: {
+      services.nginx = {
+        enable = true;
+
+        virtualHosts.test = {
+          locations = {
+            "/".return = "200 blub";
+            "/some" =  {
+              return = "200 blub";
+              extraConfig = ''
+                more_set_headers "Referrer-Policy: no-referrer";
+              '';
+            };
+          };
+          extraConfig = ''
+            more_set_headers "X-Powered-By: nixos";
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_port(80)
+
+    webserver.succeed("curl --fail --resolve test:80:127.0.0.1 --head --verbose http://test | grep -q \"X-Powered-By: nixos\"")
+    webserver.fail("curl --fail --resolve test:80:127.0.0.1 --head --verbose http://test | grep -q \"Referrer-Policy: no-referrer\"")
+
+    webserver.succeed("curl --fail --resolve test:80:127.0.0.1 --head --verbose http://test/some | grep -q \"X-Powered-By: nixos\"")
+    webserver.succeed("curl --fail --resolve test:80:127.0.0.1 --head --verbose http://test/some | grep -q \"Referrer-Policy: no-referrer\"")
+  '';
+}
diff --git a/nixos/tests/nginx-redirectcode.nix b/nixos/tests/nginx-redirectcode.nix
new file mode 100644
index 0000000000000..f60434a21a85d
--- /dev/null
+++ b/nixos/tests/nginx-redirectcode.nix
@@ -0,0 +1,25 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "nginx-redirectcode";
+  meta.maintainers = with lib.maintainers; [ misterio77 ];
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.nginx = {
+        enable = true;
+        virtualHosts.localhost = {
+          globalRedirect = "example.com/foo";
+          # With 308 (and 307), the method and body are to be kept when following it
+          redirectCode = 308;
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_port(80)
+
+    # Check the status code
+    webserver.succeed("curl -si http://localhost | grep '^HTTP/[0-9.]\+ 308 Permanent Redirect'")
+  '';
+})
diff --git a/nixos/tests/nginx-sandbox.nix b/nixos/tests/nginx-sandbox.nix
deleted file mode 100644
index 92ba30a09cf9f..0000000000000
--- a/nixos/tests/nginx-sandbox.nix
+++ /dev/null
@@ -1,65 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "nginx-sandbox";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ izorkin ];
-  };
-
-  # This test checks the creation and reading of a file in sandbox mode. Used simple lua script.
-
-  nodes.machine = { pkgs, ... }: {
-    nixpkgs.overlays = [
-      (self: super: {
-        nginx-lua = super.nginx.override {
-          modules = [
-            pkgs.nginxModules.lua
-          ];
-        };
-      })
-    ];
-    services.nginx.enable = true;
-    services.nginx.package = pkgs.nginx-lua;
-    services.nginx.virtualHosts.localhost = {
-      extraConfig = ''
-        location /test1-write {
-          content_by_lua_block {
-            local create = os.execute('${pkgs.coreutils}/bin/mkdir /tmp/test1-read')
-            local create = os.execute('${pkgs.coreutils}/bin/touch /tmp/test1-read/foo.txt')
-            local echo = os.execute('${pkgs.coreutils}/bin/echo worked > /tmp/test1-read/foo.txt')
-          }
-        }
-        location /test1-read {
-          root /tmp;
-        }
-        location /test2-write {
-          content_by_lua_block {
-            local create = os.execute('${pkgs.coreutils}/bin/mkdir /var/web/test2-read')
-            local create = os.execute('${pkgs.coreutils}/bin/touch /var/web/test2-read/bar.txt')
-            local echo = os.execute('${pkgs.coreutils}/bin/echo error-worked > /var/web/test2-read/bar.txt')
-          }
-        }
-        location /test2-read {
-          root /var/web;
-        }
-      '';
-    };
-    users.users.foo.isNormalUser = true;
-  };
-
-  testScript = ''
-    machine.wait_for_unit("nginx")
-    machine.wait_for_open_port(80)
-
-    # Checking write in temporary folder
-    machine.succeed("$(curl -vvv http://localhost/test1-write)")
-    machine.succeed('test "$(curl -fvvv http://localhost/test1-read/foo.txt)" = worked')
-
-    # Checking write in protected folder. In sandbox mode for the nginx service, the folder /var/web is mounted
-    # in read-only mode.
-    machine.succeed("mkdir -p /var/web")
-    machine.succeed("chown nginx:nginx /var/web")
-    machine.succeed("$(curl -vvv http://localhost/test2-write)")
-    assert "404 Not Found" in machine.succeed(
-        "curl -vvv -s http://localhost/test2-read/bar.txt"
-    )
-  '';
-})
diff --git a/nixos/tests/nginx-tmpdir.nix b/nixos/tests/nginx-tmpdir.nix
new file mode 100644
index 0000000000000..f26f992ffe1be
--- /dev/null
+++ b/nixos/tests/nginx-tmpdir.nix
@@ -0,0 +1,60 @@
+let
+  dst-dir = "/run/nginx-test-tmpdir-uploads";
+in
+  import ./make-test-python.nix {
+    name = "nginx-tmpdir";
+
+    nodes.machine = { pkgs, ... }: {
+      environment.etc."tmpfiles.d/nginx-uploads.conf".text = "d ${dst-dir} 0755 nginx nginx 1d";
+
+      # overwrite the tmp.conf with a short age, there will be a duplicate line info from systemd-tmpfiles in the log
+      systemd.tmpfiles.rules = [
+        "q /tmp 1777 root root 1min"
+      ];
+
+      services.nginx.enable = true;
+      # simple upload service using the nginx client body temp path
+      services.nginx.virtualHosts = {
+        localhost = {
+          locations."~ ^/upload/([0-9a-zA-Z-.]*)$" = {
+            extraConfig = ''
+              alias ${dst-dir}/$1;
+              client_body_in_file_only clean;
+              dav_methods PUT;
+              create_full_put_path on;
+              dav_access group:rw all:r;
+            '';
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("nginx")
+      machine.wait_for_open_port(80)
+
+      with subtest("Needed prerequisite --http-client-body-temp-path=/tmp/nginx_client_body and private temp"):
+        machine.succeed("touch /tmp/systemd-private-*-nginx.service-*/tmp/nginx_client_body")
+
+      with subtest("Working upload of test setup"):
+        machine.succeed("curl -X PUT http://localhost/upload/test1 --fail --data-raw 'Raw data 1'")
+        machine.succeed('test "$(cat ${dst-dir}/test1)" = "Raw data 1"')
+
+      # let the tmpfiles clean service do its job
+      machine.succeed("touch /tmp/touched")
+      machine.wait_until_succeeds(
+        "sleep 15 && systemctl start systemd-tmpfiles-clean.service && [ ! -f /tmp/touched ]",
+        timeout=150
+      )
+
+      with subtest("Working upload after cleaning"):
+        machine.succeed("curl -X PUT http://localhost/upload/test2 --fail --data-raw 'Raw data 2'")
+        machine.succeed('test "$(cat ${dst-dir}/test2)" = "Raw data 2"')
+
+      # manually remove the nginx temp dir
+      machine.succeed("rm -r --interactive=never /tmp/systemd-private-*-nginx.service-*/tmp/nginx_client_body")
+
+      with subtest("Broken upload after manual temp dir removal"):
+        machine.fail("curl -X PUT http://localhost/upload/test3 --fail --data-raw 'Raw data 3'")
+    '';
+  }
diff --git a/nixos/tests/nginx-unix-socket.nix b/nixos/tests/nginx-unix-socket.nix
new file mode 100644
index 0000000000000..4640eaa171bdf
--- /dev/null
+++ b/nixos/tests/nginx-unix-socket.nix
@@ -0,0 +1,27 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  nginxSocketPath = "/var/run/nginx/test.sock";
+in
+{
+  name = "nginx-unix-socket";
+
+  nodes = {
+    webserver = { pkgs, lib, ... }: {
+      services.nginx = {
+        enable = true;
+        virtualHosts.localhost = {
+          serverName = "localhost";
+          listen = [{ addr = "unix:${nginxSocketPath}"; }];
+          locations."/test".return = "200 'foo'";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    webserver.wait_for_unit("nginx")
+    webserver.wait_for_open_unix_socket("${nginxSocketPath}")
+
+    webserver.succeed("curl --fail --silent --unix-socket '${nginxSocketPath}' http://localhost/test | grep '^foo$'")
+  '';
+})
diff --git a/nixos/tests/nginx-variants.nix b/nixos/tests/nginx-variants.nix
index 0faa0127669dd..8c24052aacce3 100644
--- a/nixos/tests/nginx-variants.nix
+++ b/nixos/tests/nginx-variants.nix
@@ -7,17 +7,17 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 
 builtins.listToAttrs (
   builtins.map
-    (nginxName:
+    (nginxPackage:
       {
-        name = nginxName;
+        name = pkgs.lib.getName nginxPackage;
         value = makeTest {
-          name = "nginx-variant-${nginxName}";
+          name = "nginx-variant-${pkgs.lib.getName nginxPackage}";
 
           nodes.machine = { pkgs, ... }: {
             services.nginx = {
               enable = true;
               virtualHosts.localhost.locations."/".return = "200 'foo'";
-              package = pkgs."${nginxName}";
+              package = nginxPackage;
             };
           };
 
@@ -29,5 +29,5 @@ builtins.listToAttrs (
         };
       }
     )
-    [ "nginxStable" "nginxMainline" "nginxQuic" "nginxShibboleth" "openresty" "tengine" ]
+    [ pkgs.angie pkgs.angieQuic pkgs.nginxStable pkgs.nginxMainline pkgs.nginxQuic pkgs.nginxShibboleth pkgs.openresty pkgs.tengine ]
 )
diff --git a/nixos/tests/nitter.nix b/nixos/tests/nitter.nix
index 8bc55ba8c69fc..114f1aac7c7af 100644
--- a/nixos/tests/nitter.nix
+++ b/nixos/tests/nitter.nix
@@ -1,13 +1,28 @@
 import ./make-test-python.nix ({ pkgs, ... }:
 
+let
+  # In a real deployment this should naturally not common from the nix store
+  # and be seeded via agenix or as a non-nix managed file.
+  #
+  # These credentials are from the nitter wiki and are expired. We must provide
+  # credentials in the correct format, otherwise nitter fails to start. They
+  # must not be valid, as unauthorized errors are handled gracefully.
+  guestAccountFile = pkgs.writeText "guest_accounts.jsonl" ''
+    {"oauth_token":"1719213587296620928-BsXY2RIJEw7fjxoNwbBemgjJhueK0m","oauth_token_secret":"N0WB0xhL4ng6WTN44aZO82SUJjz7ssI3hHez2CUhTiYqy"}
+  '';
+in
 {
   name = "nitter";
   meta.maintainers = with pkgs.lib.maintainers; [ erdnaxe ];
 
   nodes.machine = {
-    services.nitter.enable = true;
-    # Test CAP_NET_BIND_SERVICE
-    services.nitter.server.port = 80;
+    services.nitter = {
+      enable = true;
+      # Test CAP_NET_BIND_SERVICE
+      server.port = 80;
+      # Provide dummy guest accounts
+      guestAccounts = guestAccountFile;
+    };
   };
 
   testScript = ''
diff --git a/nixos/tests/nixops/default.nix b/nixos/tests/nixops/default.nix
index b8f747b2a19f1..6501d13a2ed36 100644
--- a/nixos/tests/nixops/default.nix
+++ b/nixos/tests/nixops/default.nix
@@ -19,7 +19,7 @@ let
     passthru.override = args': testsForPackage (args // args');
   };
 
-  testLegacyNetwork = { nixopsPkg, ... }: pkgs.nixosTest ({
+  testLegacyNetwork = { nixopsPkg, ... }: pkgs.testers.nixosTest ({
     name = "nixops-legacy-network";
     nodes = {
       deployer = { config, lib, nodes, pkgs, ... }: {
diff --git a/nixos/tests/nixos-rebuild-install-bootloader.nix b/nixos/tests/nixos-rebuild-install-bootloader.nix
new file mode 100644
index 0000000000000..3ade90ea24a74
--- /dev/null
+++ b/nixos/tests/nixos-rebuild-install-bootloader.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nixos-rebuild-install-bootloader";
+
+  nodes = {
+    machine = { lib, pkgs, ... }: {
+      imports = [
+        ../modules/profiles/installation-device.nix
+        ../modules/profiles/base.nix
+      ];
+
+      nix.settings = {
+        substituters = lib.mkForce [ ];
+        hashed-mirrors = null;
+        connect-timeout = 1;
+      };
+
+      system.includeBuildDependencies = true;
+
+      virtualisation = {
+        cores = 2;
+        memorySize = 2048;
+      };
+
+      virtualisation.useBootLoader = true;
+    };
+  };
+
+  testScript =
+    let
+      configFile = pkgs.writeText "configuration.nix" ''
+        { lib, pkgs, ... }: {
+          imports = [
+            ./hardware-configuration.nix
+            <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          ];
+
+          boot.loader.grub = {
+            enable = true;
+            device = "/dev/vda";
+            forceInstall = true;
+          };
+
+          documentation.enable = false;
+        }
+      '';
+
+    in
+    ''
+      machine.start()
+      machine.succeed("udevadm settle")
+      machine.wait_for_unit("multi-user.target")
+
+      machine.succeed("nixos-generate-config")
+      machine.copy_from_host(
+          "${configFile}",
+          "/etc/nixos/configuration.nix",
+      )
+      machine.succeed("nixos-rebuild switch")
+
+      # Need to run `nixos-rebuild` twice because the first run will install
+      # GRUB anyway
+      with subtest("Switch system again and install bootloader"):
+          result = machine.succeed("nixos-rebuild switch --install-bootloader")
+          # install-grub2.pl messages
+          assert "updating GRUB 2 menu..." in result
+          assert "installing the GRUB 2 boot loader on /dev/vda..." in result
+          # GRUB message
+          assert "Installation finished. No error reported." in result
+          # at this point we've tested regression #262724, but haven't tested the bootloader itself
+          # TODO: figure out how to how to tell the test driver to start the bootloader instead of
+          # booting into the kernel directly.
+    '';
+})
diff --git a/nixos/tests/nixos-rebuild-specialisations.nix b/nixos/tests/nixos-rebuild-specialisations.nix
index 444ff7a3b9771..9192b8a8a030b 100644
--- a/nixos/tests/nixos-rebuild-specialisations.nix
+++ b/nixos/tests/nixos-rebuild-specialisations.nix
@@ -23,7 +23,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
       virtualisation = {
         cores = 2;
-        memorySize = 2048;
+        memorySize = 4096;
       };
     };
   };
diff --git a/nixos/tests/nixos-rebuild-target-host.nix b/nixos/tests/nixos-rebuild-target-host.nix
new file mode 100644
index 0000000000000..8d60b788abf38
--- /dev/null
+++ b/nixos/tests/nixos-rebuild-target-host.nix
@@ -0,0 +1,136 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "nixos-rebuild-target-host";
+
+  nodes = {
+    deployer = { lib, ... }: let
+      inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+    in {
+      imports = [ ../modules/profiles/installation-device.nix ];
+
+      nix.settings = {
+        substituters = lib.mkForce [ ];
+        hashed-mirrors = null;
+        connect-timeout = 1;
+      };
+
+      environment.systemPackages = [ pkgs.passh ];
+
+      system.includeBuildDependencies = true;
+
+      virtualisation = {
+        cores = 2;
+        memorySize = 2048;
+      };
+
+      system.build.privateKey = snakeOilPrivateKey;
+      system.build.publicKey = snakeOilPublicKey;
+    };
+
+    target = { nodes, lib, ... }: let
+      targetConfig = {
+        documentation.enable = false;
+        services.openssh.enable = true;
+
+        users.users.root.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
+        users.users.alice.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
+        users.users.bob.openssh.authorizedKeys.keys = [ nodes.deployer.system.build.publicKey ];
+
+        users.users.alice.extraGroups = [ "wheel" ];
+        users.users.bob.extraGroups = [ "wheel" ];
+
+        # Disable sudo for root to ensure sudo isn't called without `--use-remote-sudo`
+        security.sudo.extraRules = lib.mkForce [
+          { groups = [ "wheel" ]; commands = [ { command = "ALL"; } ]; }
+          { users = [ "alice" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
+        ];
+
+        nix.settings.trusted-users = [ "@wheel" ];
+      };
+    in {
+      imports = [ ./common/user-account.nix ];
+
+      config = lib.mkMerge [
+        targetConfig
+        {
+          system.build = {
+            inherit targetConfig;
+          };
+
+          networking.hostName = "target";
+        }
+      ];
+    };
+  };
+
+  testScript = { nodes, ... }:
+    let
+      sshConfig = builtins.toFile "ssh.conf" ''
+        UserKnownHostsFile=/dev/null
+        StrictHostKeyChecking=no
+      '';
+
+      targetConfigJSON = pkgs.writeText "target-configuration.json"
+        (builtins.toJSON nodes.target.system.build.targetConfig);
+
+      targetNetworkJSON = pkgs.writeText "target-network.json"
+        (builtins.toJSON nodes.target.system.build.networkConfig);
+
+      configFile = hostname: pkgs.writeText "configuration.nix" ''
+        { lib, modulesPath, ... }: {
+          imports = [
+            (modulesPath + "/virtualisation/qemu-vm.nix")
+            (modulesPath + "/testing/test-instrumentation.nix")
+            (modulesPath + "/../tests/common/user-account.nix")
+            (lib.modules.importJSON ./target-configuration.json)
+            (lib.modules.importJSON ./target-network.json)
+            ./hardware-configuration.nix
+          ];
+
+          boot.loader.grub = {
+            enable = true;
+            device = "/dev/vda";
+            forceInstall = true;
+          };
+
+          # this will be asserted
+          networking.hostName = "${hostname}";
+        }
+      '';
+    in
+    ''
+      start_all()
+      target.wait_for_open_port(22)
+
+      deployer.wait_until_succeeds("ping -c1 target")
+      deployer.succeed("install -Dm 600 ${nodes.deployer.system.build.privateKey} ~root/.ssh/id_ecdsa")
+      deployer.succeed("install ${sshConfig} ~root/.ssh/config")
+
+      target.succeed("nixos-generate-config")
+      deployer.succeed("scp alice@target:/etc/nixos/hardware-configuration.nix /root/hardware-configuration.nix")
+
+      deployer.copy_from_host("${configFile "config-1-deployed"}", "/root/configuration-1.nix")
+      deployer.copy_from_host("${configFile "config-2-deployed"}", "/root/configuration-2.nix")
+      deployer.copy_from_host("${configFile "config-3-deployed"}", "/root/configuration-3.nix")
+      deployer.copy_from_host("${targetNetworkJSON}", "/root/target-network.json")
+      deployer.copy_from_host("${targetConfigJSON}", "/root/target-configuration.json")
+
+      # Ensure sudo is disabled for root
+      target.fail("sudo true")
+
+      # This test also ensures that sudo is not called without --use-remote-sudo
+      with subtest("Deploy to root@target"):
+        deployer.succeed("nixos-rebuild switch -I nixos-config=/root/configuration-1.nix --target-host root@target &>/dev/console")
+        target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname").rstrip()
+        assert target_hostname == "config-1-deployed", f"{target_hostname=}"
+
+      with subtest("Deploy to alice@target with passwordless sudo"):
+        deployer.succeed("nixos-rebuild switch -I nixos-config=/root/configuration-2.nix --target-host alice@target --use-remote-sudo &>/dev/console")
+        target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname").rstrip()
+        assert target_hostname == "config-2-deployed", f"{target_hostname=}"
+
+      with subtest("Deploy to bob@target with password based sudo"):
+        deployer.succeed("passh -c 3 -C -p ${nodes.target.users.users.bob.password} -P \"\[sudo\] password\" nixos-rebuild switch -I nixos-config=/root/configuration-3.nix --target-host bob@target --use-remote-sudo &>/dev/console")
+        target_hostname = deployer.succeed("ssh alice@target cat /etc/hostname").rstrip()
+        assert target_hostname == "config-3-deployed", f"{target_hostname=}"
+    '';
+})
diff --git a/nixos/tests/nixos-test-driver/timeout.nix b/nixos/tests/nixos-test-driver/timeout.nix
new file mode 100644
index 0000000000000..29bd85d2498ea
--- /dev/null
+++ b/nixos/tests/nixos-test-driver/timeout.nix
@@ -0,0 +1,15 @@
+{
+  name = "Test that sleep of 6 seconds fails a timeout of 5 seconds";
+  globalTimeout = 5;
+
+  nodes = {
+    machine = ({ pkgs, ... }: {
+    });
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("sleep 6")
+  '';
+}
diff --git a/nixos/tests/nixseparatedebuginfod.nix b/nixos/tests/nixseparatedebuginfod.nix
new file mode 100644
index 0000000000000..7c192a73c7064
--- /dev/null
+++ b/nixos/tests/nixseparatedebuginfod.nix
@@ -0,0 +1,80 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  secret-key = "key-name:/COlMSRbehSh6YSruJWjL+R0JXQUKuPEn96fIb+pLokEJUjcK/2Gv8Ai96D7JGay5gDeUTx5wdpPgNvum9YtwA==";
+  public-key = "key-name:BCVI3Cv9hr/AIveg+yRmsuYA3lE8ecHaT4Db7pvWLcA=";
+in
+{
+  name = "nixseparatedebuginfod";
+  /* A binary cache with debug info and source for nix */
+  nodes.cache = { pkgs, ... }: {
+    services.nix-serve = {
+      enable = true;
+      secretKeyFile = builtins.toFile "secret-key" secret-key;
+      openFirewall = true;
+    };
+    system.extraDependencies = [
+      pkgs.nix.debug
+      pkgs.nix.src
+      pkgs.sl
+    ];
+  };
+  /* the machine where we need the debuginfo */
+  nodes.machine = {
+    imports = [
+      ../modules/installer/cd-dvd/channel.nix
+    ];
+    services.nixseparatedebuginfod.enable = true;
+    nix.settings = {
+      substituters = lib.mkForce [ "http://cache:5000" ];
+      trusted-public-keys = [ public-key ];
+    };
+    environment.systemPackages = [
+      pkgs.valgrind
+      pkgs.gdb
+      (pkgs.writeShellScriptBin "wait_for_indexation" ''
+        set -x
+        while debuginfod-find debuginfo /run/current-system/sw/bin/nix |& grep 'File too large'; do
+          sleep 1;
+        done
+      '')
+    ];
+  };
+  testScript = ''
+    start_all()
+    cache.wait_for_unit("nix-serve.service")
+    cache.wait_for_open_port(5000)
+    machine.wait_for_unit("nixseparatedebuginfod.service")
+    machine.wait_for_open_port(1949)
+
+    with subtest("show the config to debug the test"):
+      machine.succeed("nix --extra-experimental-features nix-command show-config |& logger")
+      machine.succeed("cat /etc/nix/nix.conf |& logger")
+    with subtest("check that the binary cache works"):
+      machine.succeed("nix-store -r ${pkgs.sl}")
+
+    # nixseparatedebuginfod needs .drv to associate executable -> source
+    # on regular systems this would be provided by nixos-rebuild
+    machine.succeed("nix-instantiate '<nixpkgs>' -A nix")
+
+    machine.succeed("timeout 600 wait_for_indexation")
+
+    # test debuginfod-find
+    machine.succeed("debuginfod-find debuginfo /run/current-system/sw/bin/nix")
+
+    # test that gdb can fetch source
+    out = machine.succeed("gdb /run/current-system/sw/bin/nix --batch -x ${builtins.toFile "commands" ''
+    start
+    l
+    ''}")
+    print(out)
+    assert 'int main(' in out
+
+    # test that valgrind can display location information
+    # this relies on the fact that valgrind complains about nix
+    # libgc helps in this regard, and we also ask valgrind to show leak kinds
+    # which are usually false positives.
+    out = machine.succeed("valgrind --leak-check=full --show-leak-kinds=all nix-env --version 2>&1")
+    print(out)
+    assert 'main.cc' in out
+  '';
+})
diff --git a/nixos/tests/non-switchable-system.nix b/nixos/tests/non-switchable-system.nix
new file mode 100644
index 0000000000000..54bede75453ba
--- /dev/null
+++ b/nixos/tests/non-switchable-system.nix
@@ -0,0 +1,15 @@
+{ lib, ... }:
+
+{
+  name = "non-switchable-system";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = {
+    system.switch.enable = false;
+  };
+
+  testScript = ''
+    machine.succeed("test ! -e /run/current-system/bin/switch-to-configuration")
+  '';
+}
diff --git a/nixos/tests/noto-fonts.nix b/nixos/tests/noto-fonts.nix
index edbb0db4cb7aa..b871f5f517294 100644
--- a/nixos/tests/noto-fonts.nix
+++ b/nixos/tests/noto-fonts.nix
@@ -11,7 +11,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         noto-fonts
         noto-fonts-cjk-sans
         noto-fonts-cjk-serif
-        noto-fonts-emoji
+        noto-fonts-color-emoji
       ];
       fontconfig.defaultFonts = {
         serif = [ "Noto Serif" "Noto Serif CJK SC" ];
diff --git a/nixos/tests/npmrc.nix b/nixos/tests/npmrc.nix
new file mode 100644
index 0000000000000..dbf24d372feb4
--- /dev/null
+++ b/nixos/tests/npmrc.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ ... }:
+let
+  machineName = "machine";
+  settingName = "prefix";
+  settingValue = "/some/path";
+in
+{
+  name = "npmrc";
+
+  nodes."${machineName}".programs.npm = {
+    enable = true;
+    npmrc = ''
+      ${settingName} = ${settingValue}
+    '';
+  };
+
+  testScript = ''
+    ${machineName}.start()
+
+    assert ${machineName}.succeed("npm config get ${settingName}") == "${settingValue}\n"
+  '';
+})
diff --git a/nixos/tests/ntfy-sh-migration.nix b/nixos/tests/ntfy-sh-migration.nix
new file mode 100644
index 0000000000000..de6660052d679
--- /dev/null
+++ b/nixos/tests/ntfy-sh-migration.nix
@@ -0,0 +1,77 @@
+# the ntfy-sh module was switching to DynamicUser=true. this test assures that
+# the migration does not break existing setups.
+#
+# this test works doing a migration and asserting ntfy-sh runs properly. first,
+# ntfy-sh is configured to use a static user and group. then ntfy-sh is
+# started and tested. after that, ntfy-sh is shut down and a systemd drop
+# in configuration file is used to upate the service configuration to use
+# DynamicUser=true. then the ntfy-sh is started again and tested.
+
+import ./make-test-python.nix {
+  name = "ntfy-sh";
+
+  nodes.machine = {
+    lib,
+    pkgs,
+    ...
+  }: {
+    environment.etc."ntfy-sh-dynamic-user.conf".text = ''
+      [Service]
+      Group=new-ntfy-sh
+      User=new-ntfy-sh
+      DynamicUser=true
+    '';
+
+    services.ntfy-sh.enable = true;
+    services.ntfy-sh.settings.base-url = "http://localhost:2586";
+
+    systemd.services.ntfy-sh.serviceConfig = {
+      DynamicUser = lib.mkForce false;
+      ExecStartPre = [
+        "${pkgs.coreutils}/bin/id"
+        "${pkgs.coreutils}/bin/ls -lahd /var/lib/ntfy-sh/"
+        "${pkgs.coreutils}/bin/ls -lah /var/lib/ntfy-sh/"
+      ];
+      Group = lib.mkForce "old-ntfy-sh";
+      User = lib.mkForce "old-ntfy-sh";
+    };
+
+    users.users.old-ntfy-sh = {
+      isSystemUser = true;
+      group = "old-ntfy-sh";
+    };
+
+    users.groups.old-ntfy-sh = {};
+  };
+
+  testScript = ''
+    import json
+
+    msg = "Test notification"
+
+    def test_ntfysh():
+      machine.wait_for_unit("ntfy-sh.service")
+      machine.wait_for_open_port(2586)
+
+      machine.succeed(f"curl -d '{msg}' localhost:2586/test")
+
+      text = machine.succeed("curl -s localhost:2586/test/json?poll=1")
+      for line in text.splitlines():
+        notif = json.loads(line)
+        assert msg == notif["message"], "Wrong message"
+
+      machine.succeed("ntfy user list")
+
+    machine.wait_for_unit("multi-user.target")
+
+    test_ntfysh()
+
+    machine.succeed("systemctl stop ntfy-sh.service")
+    machine.succeed("mkdir -p /run/systemd/system/ntfy-sh.service.d")
+    machine.succeed("cp /etc/ntfy-sh-dynamic-user.conf /run/systemd/system/ntfy-sh.service.d/dynamic-user.conf")
+    machine.succeed("systemctl daemon-reload")
+    machine.succeed("systemctl start ntfy-sh.service")
+
+    test_ntfysh()
+  '';
+}
diff --git a/nixos/tests/ntpd-rs.nix b/nixos/tests/ntpd-rs.nix
new file mode 100644
index 0000000000000..6f3c80e87f072
--- /dev/null
+++ b/nixos/tests/ntpd-rs.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({ lib, ... }:
+{
+  name = "ntpd-rs";
+
+  meta = {
+    maintainers = with lib.maintainers; [ fpletz ];
+  };
+
+  nodes = {
+    client = {
+      services.ntpd-rs = {
+        enable = true;
+        metrics.enable = true;
+        useNetworkingTimeServers = false;
+        settings = {
+          source = [
+            {
+              mode = "server";
+              address = "server";
+            }
+          ];
+          synchronization = {
+            minimum-agreeing-sources = 1;
+          };
+        };
+      };
+    };
+    server = {
+      networking.firewall.allowedUDPPorts = [ 123 ];
+      services.ntpd-rs = {
+        enable = true;
+        metrics.enable = true;
+        settings = {
+          server = [
+            { listen = "[::]:123"; }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    for machine in (server, client):
+      machine.wait_for_unit('multi-user.target')
+      machine.succeed('systemctl is-active ntpd-rs.service')
+      machine.succeed('systemctl is-active ntpd-rs-metrics.service')
+      machine.succeed('curl http://localhost:9975/metrics | grep ntp_uptime_seconds')
+  '';
+})
diff --git a/nixos/tests/oci-containers.nix b/nixos/tests/oci-containers.nix
index 1afa9df36dfa4..205ce623d089c 100644
--- a/nixos/tests/oci-containers.nix
+++ b/nixos/tests/oci-containers.nix
@@ -12,7 +12,7 @@ let
     name = "oci-containers-${backend}";
 
     meta.maintainers = lib.teams.serokell.members
-                       ++ (with lib.maintainers; [ adisbladis benley mkaito ]);
+                       ++ (with lib.maintainers; [ benley mkaito ]);
 
     nodes = {
       ${backend} = { pkgs, ... }: {
diff --git a/nixos/tests/ocsinventory-agent.nix b/nixos/tests/ocsinventory-agent.nix
new file mode 100644
index 0000000000000..67b0c8c911036
--- /dev/null
+++ b/nixos/tests/ocsinventory-agent.nix
@@ -0,0 +1,33 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "ocsinventory-agent";
+
+  nodes.machine = { pkgs, ... }: {
+    services.ocsinventory-agent = {
+      enable = true;
+      settings = {
+        debug = true;
+        local = "/var/lib/ocsinventory-agent/reports";
+        tag = "MY_INVENTORY_TAG";
+      };
+    };
+  };
+
+  testScript = ''
+    path = "/var/lib/ocsinventory-agent/reports"
+
+    # Run the agent to generate the inventory file in offline mode
+    start_all()
+    machine.succeed("mkdir -p {}".format(path))
+    machine.wait_for_unit("ocsinventory-agent.service")
+    machine.wait_until_succeeds("journalctl -u ocsinventory-agent.service | grep 'Inventory saved in'")
+
+    # Fetch the path to the generated inventory file
+    report_file = machine.succeed("find {}/*.ocs -type f | head -n1".format(path))
+
+    with subtest("Check the tag value"):
+      tag = machine.succeed(
+        "${pkgs.libxml2}/bin/xmllint --xpath 'string(/REQUEST/CONTENT/ACCOUNTINFO/KEYVALUE)' {}".format(report_file)
+      ).rstrip()
+      assert tag == "MY_INVENTORY_TAG", f"tag is not valid, was '{tag}'"
+  '';
+})
diff --git a/nixos/tests/openresty-lua.nix b/nixos/tests/openresty-lua.nix
index b177b3c194d78..9e987398f51d7 100644
--- a/nixos/tests/openresty-lua.nix
+++ b/nixos/tests/openresty-lua.nix
@@ -16,6 +16,12 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
     nodes = {
       webserver = { pkgs, lib, ... }: {
+        networking = {
+          extraHosts = ''
+            127.0.0.1 default.test
+            127.0.0.1 sandbox.test
+          '';
+        };
         services.nginx = {
           enable = true;
           package = pkgs.openresty;
@@ -24,7 +30,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
             lua_package_path '${luaPath};;';
           '';
 
-          virtualHosts."default" = {
+          virtualHosts."default.test" = {
             default = true;
             locations."/" = {
               extraConfig = ''
@@ -36,6 +42,33 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
               '';
             };
           };
+
+          virtualHosts."sandbox.test" = {
+            locations."/test1-write" = {
+              extraConfig = ''
+                content_by_lua_block {
+                  local create = os.execute('${pkgs.coreutils}/bin/mkdir /tmp/test1-read')
+                  local create = os.execute('${pkgs.coreutils}/bin/touch /tmp/test1-read/foo.txt')
+                  local echo = os.execute('${pkgs.coreutils}/bin/echo worked > /tmp/test1-read/foo.txt')
+                }
+              '';
+            };
+            locations."/test1-read" = {
+              root = "/tmp";
+            };
+            locations."/test2-write" = {
+              extraConfig = ''
+                content_by_lua_block {
+                  local create = os.execute('${pkgs.coreutils}/bin/mkdir /var/web/test2-read')
+                  local create = os.execute('${pkgs.coreutils}/bin/touch /var/web/test2-read/bar.txt')
+                  local echo = os.execute('${pkgs.coreutils}/bin/echo error-worked > /var/web/test2-read/bar.txt')
+                }
+              '';
+            };
+            locations."/test2-read" = {
+              root = "/var/web";
+            };
+          };
         };
       };
     };
@@ -51,5 +84,18 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
           f"curl -w '%{{http_code}}' --head --fail {url}"
         )
         assert http_code.split("\n")[-1] == "200"
+
+        # This test checks the creation and reading of a file in sandbox mode.
+        # Checking write in temporary folder
+        webserver.succeed("$(curl -vvv http://sandbox.test/test1-write)")
+        webserver.succeed('test "$(curl -fvvv http://sandbox.test/test1-read/foo.txt)" = worked')
+        # Checking write in protected folder. In sandbox mode for the nginx service, the folder /var/web is mounted
+        # in read-only mode.
+        webserver.succeed("mkdir -p /var/web")
+        webserver.succeed("chown nginx:nginx /var/web")
+        webserver.succeed("$(curl -vvv http://sandbox.test/test2-write)")
+        assert "404 Not Found" in machine.succeed(
+            "curl -vvv -s http://sandbox.test/test2-read/bar.txt"
+        )
       '';
   })
diff --git a/nixos/tests/opensearch.nix b/nixos/tests/opensearch.nix
index c0caf950cb9c9..2887ac9677656 100644
--- a/nixos/tests/opensearch.nix
+++ b/nixos/tests/opensearch.nix
@@ -31,14 +31,9 @@ in
       services.opensearch.dataDir = "/var/opensearch_test";
       services.opensearch.user = "open_search";
       services.opensearch.group = "open_search";
-      system.activationScripts.createDirectory = {
-        text = ''
-          mkdir -p "/var/opensearch_test"
-          chown open_search:open_search /var/opensearch_test
-          chmod 0700 /var/opensearch_test
-        '';
-        deps = [ "users" "groups" ];
-      };
+      systemd.tmpfiles.rules = [
+        "d /var/opensearch_test 0700 open_search open_search -"
+      ];
       users = {
         groups.open_search = {};
         users.open_search = {
diff --git a/nixos/tests/opensmtpd-rspamd.nix b/nixos/tests/opensmtpd-rspamd.nix
index 19969a7b47ddd..e413a2050bd61 100644
--- a/nixos/tests/opensmtpd-rspamd.nix
+++ b/nixos/tests/opensmtpd-rspamd.nix
@@ -119,6 +119,7 @@ import ./make-test-python.nix {
   testScript = ''
     start_all()
 
+    client.systemctl("start network-online.target")
     client.wait_for_unit("network-online.target")
     smtp1.wait_for_unit("opensmtpd")
     smtp2.wait_for_unit("opensmtpd")
diff --git a/nixos/tests/opensmtpd.nix b/nixos/tests/opensmtpd.nix
index 17c1a569ba0d9..d32f82ed33b8c 100644
--- a/nixos/tests/opensmtpd.nix
+++ b/nixos/tests/opensmtpd.nix
@@ -104,6 +104,7 @@ import ./make-test-python.nix {
   testScript = ''
     start_all()
 
+    client.systemctl("start network-online.target")
     client.wait_for_unit("network-online.target")
     smtp1.wait_for_unit("opensmtpd")
     smtp2.wait_for_unit("opensmtpd")
diff --git a/nixos/tests/openssh.nix b/nixos/tests/openssh.nix
index 4083f5906d79a..8074fd2ed4838 100644
--- a/nixos/tests/openssh.nix
+++ b/nixos/tests/openssh.nix
@@ -22,7 +22,7 @@ in {
         ];
       };
 
-    server_lazy =
+    server-lazy =
       { ... }:
 
       {
@@ -34,7 +34,20 @@ in {
         ];
       };
 
-    server_localhost_only =
+    server-lazy-socket = {
+      virtualisation.vlans = [ 1 2 ];
+      services.openssh = {
+        enable = true;
+        startWhenNeeded = true;
+        ports = [ 2222 ];
+        listenAddresses = [ { addr = "0.0.0.0"; } ];
+      };
+      users.users.root.openssh.authorizedKeys.keys = [
+        snakeOilPublicKey
+      ];
+    };
+
+    server-localhost-only =
       { ... }:
 
       {
@@ -43,7 +56,7 @@ in {
         };
       };
 
-    server_localhost_only_lazy =
+    server-localhost-only-lazy =
       { ... }:
 
       {
@@ -52,15 +65,66 @@ in {
         };
       };
 
+    server-match-rule =
+      { ... }:
+
+      {
+        services.openssh = {
+          enable = true; listenAddresses = [ { addr = "127.0.0.1"; port = 22; } { addr = "[::]"; port = 22; } ];
+          extraConfig = ''
+            # Combined test for two (predictable) Match criterias
+            Match LocalAddress 127.0.0.1 LocalPort 22
+              PermitRootLogin yes
+
+            # Separate tests for Match criterias
+            Match User root
+              PermitRootLogin yes
+            Match Group root
+              PermitRootLogin yes
+            Match Host nohost.example
+              PermitRootLogin yes
+            Match LocalAddress 127.0.0.1
+              PermitRootLogin yes
+            Match LocalPort 22
+              PermitRootLogin yes
+            Match RDomain nohost.example
+              PermitRootLogin yes
+            Match Address 127.0.0.1
+              PermitRootLogin yes
+          '';
+        };
+      };
+
+    server_allowedusers =
+      { ... }:
+
+      {
+        services.openssh = { enable = true; settings.AllowUsers = [ "alice" "bob" ]; };
+        users.groups = { alice = { }; bob = { }; carol = { }; };
+        users.users = {
+          alice = { isNormalUser = true; group = "alice"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+          bob = { isNormalUser = true; group = "bob"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+          carol = { isNormalUser = true; group = "carol"; openssh.authorizedKeys.keys = [ snakeOilPublicKey ]; };
+        };
+      };
+
     client =
-      { ... }: { };
+      { ... }: {
+        virtualisation.vlans = [ 1 2 ];
+      };
 
   };
 
   testScript = ''
     start_all()
 
-    server.wait_for_unit("sshd")
+    server.wait_for_unit("sshd", timeout=30)
+    server_localhost_only.wait_for_unit("sshd", timeout=30)
+    server_match_rule.wait_for_unit("sshd", timeout=30)
+
+    server_lazy.wait_for_unit("sshd.socket", timeout=30)
+    server_localhost_only_lazy.wait_for_unit("sshd.socket", timeout=30)
+    server_lazy_socket.wait_for_unit("sshd.socket", timeout=30)
 
     with subtest("manual-authkey"):
         client.succeed("mkdir -m 700 /root/.ssh")
@@ -89,11 +153,21 @@ in {
         )
 
         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("socket activation on a non-standard port"):
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ssh -p 2222 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.2.4 true",
             timeout=30
         )
 
@@ -107,12 +181,33 @@ in {
             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"):
         server_localhost_only.succeed("ss -nlt | grep '127.0.0.1:22'")
         server_localhost_only_lazy.succeed("ss -nlt | grep '127.0.0.1:22'")
+
+    with subtest("match-rules"):
+        server_match_rule.succeed("ss -nlt | grep '127.0.0.1:22'")
+
+    with subtest("allowed-users"):
+        client.succeed(
+            "cat ${snakeOilPrivateKey} > privkey.snakeoil"
+        )
+        client.succeed("chmod 600 privkey.snakeoil")
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil alice@server_allowedusers true",
+            timeout=30
+        )
+        client.succeed(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil bob@server_allowedusers true",
+            timeout=30
+        )
+        client.fail(
+            "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil carol@server_allowedusers true",
+            timeout=30
+        )
   '';
 })
diff --git a/nixos/tests/opentabletdriver.nix b/nixos/tests/opentabletdriver.nix
index b7583f6dd2648..a71a007c41100 100644
--- a/nixos/tests/opentabletdriver.nix
+++ b/nixos/tests/opentabletdriver.nix
@@ -20,9 +20,11 @@ in {
     ''
       machine.start()
       machine.wait_for_x()
+
+      machine.wait_for_unit('graphical.target')
       machine.wait_for_unit("opentabletdriver.service", "${testUser}")
 
-      machine.succeed("cat /etc/udev/rules.d/99-opentabletdriver.rules")
+      machine.succeed("cat /etc/udev/rules.d/70-opentabletdriver.rules")
       # Will fail if service is not running
       # Needs to run as the same user that started the service
       machine.succeed("su - ${testUser} -c 'otd detect'")
diff --git a/nixos/tests/os-prober.nix b/nixos/tests/os-prober.nix
index dae1306bd69d0..034de0620d885 100644
--- a/nixos/tests/os-prober.nix
+++ b/nixos/tests/os-prober.nix
@@ -95,7 +95,7 @@ in {
           ntp
           perlPackages.ListCompare
           perlPackages.XMLLibXML
-          python3Minimal
+          python3
           shared-mime-info
           stdenv
           sudo
diff --git a/nixos/tests/osquery.nix b/nixos/tests/osquery.nix
index 9aa9820e50c52..e98e7c1baf048 100644
--- a/nixos/tests/osquery.nix
+++ b/nixos/tests/osquery.nix
@@ -36,7 +36,7 @@ in
       machine.succeed("echo 'SELECT address FROM etc_hosts LIMIT 1;' | osqueryi | tee /dev/console | grep -q '127.0.0.1'")
 
       # osquery binaries respect configuration from the Nix config option.
-      machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"utc\";' | osqueryi | tee /dev/console | grep -q ${boolToString utc}")
+      machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"utc\";' | osqueryi | tee /dev/console | grep -q ${lib.boolToString utc}")
 
       # osquery binaries respect configuration from the Nix flags option.
       machine.succeed("echo 'SELECT value FROM osquery_flags WHERE name = \"config_refresh\";' | osqueryi | tee /dev/console | grep -q ${config_refresh}")
diff --git a/nixos/tests/owncast.nix b/nixos/tests/owncast.nix
index debb34f5009dc..73aac4e704751 100644
--- a/nixos/tests/owncast.nix
+++ b/nixos/tests/owncast.nix
@@ -31,6 +31,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
   testScript = ''
     start_all()
 
+    client.systemctl("start network-online.target")
+    server.systemctl("start network-online.target")
     client.wait_for_unit("network-online.target")
     server.wait_for_unit("network-online.target")
     server.wait_for_unit("owncast.service")
diff --git a/nixos/tests/pam/pam-file-contents.nix b/nixos/tests/pam/pam-file-contents.nix
index 2bafd90618e97..accaa4cc70a94 100644
--- a/nixos/tests/pam/pam-file-contents.nix
+++ b/nixos/tests/pam/pam-file-contents.nix
@@ -7,7 +7,7 @@ import ../make-test-python.nix ({ pkgs, ... }: {
   nodes.machine = { ... }: {
     imports = [ ../../modules/profiles/minimal.nix ];
 
-    krb5.enable = true;
+    security.krb5.enable = true;
 
     users = {
       mutableUsers = false;
diff --git a/nixos/tests/pam/pam-u2f.nix b/nixos/tests/pam/pam-u2f.nix
index 07408dea797e4..46e307a3f125a 100644
--- a/nixos/tests/pam/pam-u2f.nix
+++ b/nixos/tests/pam/pam-u2f.nix
@@ -20,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.*origin=nixos-test" /etc/pam.d/ -R'
+          'egrep "auth required .*/lib/security/pam_u2f.so.*cue.*debug.*interactive.*origin=nixos-test" /etc/pam.d/ -R'
       )
     '';
 })
diff --git a/nixos/tests/pam/test_chfn.py b/nixos/tests/pam/test_chfn.py
index a48438b8d305f..3cfbb3908e9d2 100644
--- a/nixos/tests/pam/test_chfn.py
+++ b/nixos/tests/pam/test_chfn.py
@@ -6,7 +6,7 @@ expected_lines = {
     "auth required pam_deny.so",
     "auth sufficient @@pam_ccreds@@/lib/security/pam_ccreds.so action=store use_first_pass",
     "auth sufficient pam_rootok.so",
-    "auth sufficient pam_unix.so   likeauth try_first_pass",
+    "auth sufficient pam_unix.so likeauth try_first_pass",
     "password sufficient @@pam_krb5@@/lib/security/pam_krb5.so use_first_pass",
     "password sufficient pam_unix.so nullok yescrypt",
     "session optional @@pam_krb5@@/lib/security/pam_krb5.so",
@@ -15,9 +15,10 @@ expected_lines = {
 }
 actual_lines = set(machine.succeed("cat /etc/pam.d/chfn").splitlines())
 
-missing_lines = expected_lines - actual_lines
-extra_lines = actual_lines - expected_lines
-non_functional_lines = set([line for line in extra_lines if (line == "" or line.startswith("#"))])
+stripped_lines = set([line.split("#")[0].rstrip() for line in actual_lines])
+missing_lines = expected_lines - stripped_lines
+extra_lines = stripped_lines - expected_lines
+non_functional_lines = set([line for line in extra_lines if line == ""])
 unexpected_functional_lines = extra_lines - non_functional_lines
 
 with subtest("All expected lines are in the file"):
diff --git a/nixos/tests/pantheon.nix b/nixos/tests/pantheon.nix
index dee6964644c5d..69a28c397bedc 100644
--- a/nixos/tests/pantheon.nix
+++ b/nixos/tests/pantheon.nix
@@ -26,6 +26,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
 
     with subtest("Test we can see usernames in elementary-greeter"):
         machine.wait_for_text("${user.description}")
+        machine.wait_until_succeeds("pgrep -f io.elementary.greeter-compositor")
         # OCR was struggling with this one.
         # machine.wait_for_text("${bob.description}")
         # Ensure the password box is focused by clicking it.
@@ -39,17 +40,40 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
         machine.wait_for_x()
         machine.wait_for_file("${user.home}/.Xauthority")
         machine.succeed("xauth merge ${user.home}/.Xauthority")
+        machine.wait_until_succeeds('journalctl -t gnome-session-binary --grep "Entering running state"')
 
     with subtest("Check that logging in has given the user ownership of devices"):
         machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
 
-    with subtest("Check if pantheon session components actually start"):
-        machine.wait_until_succeeds("pgrep gala")
-        machine.wait_for_window("gala")
-        machine.wait_until_succeeds("pgrep -f io.elementary.wingpanel")
-        machine.wait_for_window("io.elementary.wingpanel")
-        machine.wait_until_succeeds("pgrep plank")
-        machine.wait_for_window("plank")
+    with subtest("Check if Pantheon components actually start"):
+        for i in ["gala", "io.elementary.wingpanel", "plank", "gsd-media-keys", "io.elementary.desktop.agent-polkit"]:
+            machine.wait_until_succeeds(f"pgrep -f {i}")
+        for i in ["gala", "io.elementary.wingpanel", "plank"]:
+            machine.wait_for_window(i)
+        machine.wait_for_unit("bamfdaemon.service", "${user.name}")
+        machine.wait_for_unit("io.elementary.files.xdg-desktop-portal.service", "${user.name}")
+
+    with subtest("Check if various environment variables are set"):
+        cmd = "xargs --null --max-args=1 echo < /proc/$(pgrep -xf /run/current-system/sw/bin/gala)/environ"
+        machine.succeed(f"{cmd} | grep 'XDG_CURRENT_DESKTOP' | grep 'Pantheon'")
+        # Hopefully from the sessionPath option.
+        machine.succeed(f"{cmd} | grep 'XDG_DATA_DIRS' | grep 'gsettings-schemas/pantheon-agent-geoclue2'")
+        # Hopefully from login shell.
+        machine.succeed(f"{cmd} | grep '__NIXOS_SET_ENVIRONMENT_DONE' | grep '1'")
+        # See elementary-session-settings packaging.
+        machine.succeed(f"{cmd} | grep 'XDG_CONFIG_DIRS' | grep 'elementary-default-settings'")
+
+    with subtest("Open elementary videos"):
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.videos >&2 &'")
+        machine.sleep(2)
+        machine.wait_for_window("io.elementary.videos")
+        machine.wait_for_text("No Videos Open")
+
+    with subtest("Open elementary calendar"):
+        machine.wait_until_succeeds("pgrep -f evolution-calendar-factory")
+        machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.calendar >&2 &'")
+        machine.sleep(2)
+        machine.wait_for_window("io.elementary.calendar")
 
     with subtest("Open system settings"):
         machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.switchboard >&2 &'")
@@ -61,9 +85,19 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
         machine.execute("su - ${user.name} -c 'DISPLAY=:0 io.elementary.terminal >&2 &'")
         machine.wait_for_window("io.elementary.terminal")
 
+    with subtest("Trigger multitasking view"):
+        cmd = "dbus-send --session --dest=org.pantheon.gala --print-reply /org/pantheon/gala org.pantheon.gala.PerformAction int32:1"
+        env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0"
+        machine.succeed(f"su - ${user.name} -c '{env} {cmd}'")
+        machine.sleep(3)
+        machine.screenshot("multitasking")
+        machine.succeed(f"su - ${user.name} -c '{env} {cmd}'")
+
     with subtest("Check if gala has ever coredumped"):
         machine.fail("coredumpctl --json=short | grep gala")
-        machine.sleep(20)
+        # So you can see the dock in the below screenshot.
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 xdotool mousemove 450 1000 >&2 &'")
+        machine.sleep(10)
         machine.screenshot("screen")
   '';
 })
diff --git a/nixos/tests/paperless.nix b/nixos/tests/paperless.nix
index ce6a4d8128dfd..3d834b29958de 100644
--- a/nixos/tests/paperless.nix
+++ b/nixos/tests/paperless.nix
@@ -2,65 +2,88 @@ import ./make-test-python.nix ({ lib, ... }: {
   name = "paperless";
   meta.maintainers = with lib.maintainers; [ erikarvstedt Flakebi ];
 
-  nodes.machine = { pkgs, ... }: {
-    environment.systemPackages = with pkgs; [ imagemagick jq ];
-    services.paperless = {
-      enable = true;
-      passwordFile = builtins.toFile "password" "admin";
+  nodes = let self = {
+    simple = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [ imagemagick jq ];
+      services.paperless = {
+        enable = true;
+        passwordFile = builtins.toFile "password" "admin";
+      };
     };
-  };
+    postgres = { config, pkgs, ... }: {
+      imports = [ self.simple ];
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "paperless" ];
+        ensureUsers = [
+          { name = config.services.paperless.user;
+            ensureDBOwnership = true;
+          }
+        ];
+      };
+      services.paperless.settings = {
+        PAPERLESS_DBHOST = "/run/postgresql";
+      };
+    };
+  }; in self;
 
   testScript = ''
     import json
 
-    machine.wait_for_unit("paperless-consumer.service")
+    def test_paperless(node):
+      node.wait_for_unit("paperless-consumer.service")
 
-    with subtest("Add a document via the file system"):
-        machine.succeed(
-            "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
-            "-annotate +5+20 'hello world 16-10-2005' /var/lib/paperless/consume/doc.png"
+      with subtest("Add a document via the file system"):
+        node.succeed(
+          "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
+          "-annotate +5+20 'hello world 16-10-2005' /var/lib/paperless/consume/doc.png"
         )
 
-    with subtest("Web interface gets ready"):
-        machine.wait_for_unit("paperless-web.service")
+      with subtest("Web interface gets ready"):
+        node.wait_for_unit("paperless-web.service")
         # Wait until server accepts connections
-        machine.wait_until_succeeds("curl -fs localhost:28981")
+        node.wait_until_succeeds("curl -fs localhost:28981")
 
-    # Required for consuming documents via the web interface
-    with subtest("Task-queue gets ready"):
-        machine.wait_for_unit("paperless-task-queue.service")
+      # Required for consuming documents via the web interface
+      with subtest("Task-queue gets ready"):
+        node.wait_for_unit("paperless-task-queue.service")
 
-    with subtest("Add a png document via the web interface"):
-        machine.succeed(
-            "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
-            "-annotate +5+20 'hello web 16-10-2005' /tmp/webdoc.png"
+      with subtest("Add a png document via the web interface"):
+        node.succeed(
+          "convert -size 400x40 xc:white -font 'DejaVu-Sans' -pointsize 20 -fill black "
+          "-annotate +5+20 'hello web 16-10-2005' /tmp/webdoc.png"
         )
-        machine.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.png -fs localhost:28981/api/documents/post_document/")
+        node.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.png -fs localhost:28981/api/documents/post_document/")
 
-    with subtest("Add a txt document via the web interface"):
-        machine.succeed(
-            "echo 'hello web 16-10-2005' > /tmp/webdoc.txt"
+      with subtest("Add a txt document via the web interface"):
+        node.succeed(
+          "echo 'hello web 16-10-2005' > /tmp/webdoc.txt"
         )
-        machine.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.txt -fs localhost:28981/api/documents/post_document/")
+        node.wait_until_succeeds("curl -u admin:admin -F document=@/tmp/webdoc.txt -fs localhost:28981/api/documents/post_document/")
 
-    with subtest("Documents are consumed"):
-        machine.wait_until_succeeds(
-            "(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 3))"
+      with subtest("Documents are consumed"):
+        node.wait_until_succeeds(
+          "(($(curl -u admin:admin -fs localhost:28981/api/documents/ | jq .count) == 3))"
         )
-        docs = json.loads(machine.succeed("curl -u admin:admin -fs localhost:28981/api/documents/"))['results']
+        docs = json.loads(node.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']
         assert "2005-10-16" in docs[2]['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/"))
+      # Detects gunicorn issues, see PR #190888
+      with subtest("Document metadata can be accessed"):
+        metadata = json.loads(node.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/"))
+        metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/2/metadata/"))
         assert "original_checksum" in metadata
 
-        metadata = json.loads(machine.succeed("curl -u admin:admin -fs localhost:28981/api/documents/3/metadata/"))
+        metadata = json.loads(node.succeed("curl -u admin:admin -fs localhost:28981/api/documents/3/metadata/"))
         assert "original_checksum" in metadata
+
+    test_paperless(simple)
+    simple.send_monitor_command("quit")
+    simple.wait_for_shutdown()
+    test_paperless(postgres)
   '';
 })
diff --git a/nixos/tests/pgadmin4.nix b/nixos/tests/pgadmin4.nix
index cb8de87c9ee31..b726a3eb3b946 100644
--- a/nixos/tests/pgadmin4.nix
+++ b/nixos/tests/pgadmin4.nix
@@ -4,39 +4,49 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
   name = "pgadmin4";
   meta.maintainers = with lib.maintainers; [ mkg20001 gador ];
 
-  nodes.machine = { pkgs, ... }: {
-
-    imports = [ ./common/user-account.nix ];
-
-    environment.systemPackages = with pkgs; [
-      wget
-      curl
-      pgadmin4-desktopmode
-    ];
-
-    services.postgresql = {
-      enable = true;
-      authentication = ''
-        host    all             all             localhost               trust
-      '';
-      ensureUsers = [
-        {
-          name = "postgres";
-          ensurePermissions = {
-            "DATABASE \"postgres\"" = "ALL PRIVILEGES";
-          };
-        }
+  nodes = {
+    machine = { pkgs, ... }: {
+
+      imports = [ ./common/user-account.nix ];
+
+      environment.systemPackages = with pkgs; [
+        wget
+        curl
+        pgadmin4-desktopmode
       ];
+
+      services.postgresql = {
+        enable = true;
+        authentication = ''
+          host    all             all             localhost               trust
+        '';
+      };
+
+      services.pgadmin = {
+        port = 5051;
+        enable = true;
+        initialEmail = "bruh@localhost.de";
+        initialPasswordFile = pkgs.writeText "pw" "bruh2012!";
+      };
     };
+    machine2 = { pkgs, ... }: {
+
+      imports = [ ./common/user-account.nix ];
+
+      services.postgresql = {
+        enable = true;
+      };
 
-    services.pgadmin = {
-      port = 5051;
-      enable = true;
-      initialEmail = "bruh@localhost.de";
-      initialPasswordFile = pkgs.writeText "pw" "bruh2012!";
+      services.pgadmin = {
+        enable = true;
+        initialEmail = "bruh@localhost.de";
+        initialPasswordFile = pkgs.writeText "pw" "bruh2012!";
+        minimumPasswordLength = 12;
+      };
     };
   };
 
+
   testScript = ''
     with subtest("Check pgadmin module"):
       machine.wait_for_unit("postgresql")
@@ -45,6 +55,11 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       machine.wait_until_succeeds("curl -sS localhost:5051/login | grep \"<title>pgAdmin 4</title>\" > /dev/null")
       # check for missing support files (css, js etc). Should catch not-generated files during build. See e.g. https://github.com/NixOS/nixpkgs/pull/229184
       machine.succeed("wget -nv --level=1 --spider --recursive localhost:5051/login")
+      # test idempotenceny
+      machine.systemctl("restart pgadmin.service")
+      machine.wait_for_unit("pgadmin")
+      machine.wait_until_succeeds("curl -sS localhost:5051")
+      machine.wait_until_succeeds("curl -sS localhost:5051/login | grep \"<title>pgAdmin 4</title>\" > /dev/null")
 
     # pgadmin4 module saves the configuration to /etc/pgadmin/config_system.py
     # pgadmin4-desktopmode tries to read that as well. This normally fails with a PermissionError, as the config file
@@ -57,5 +72,9 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       machine.wait_until_succeeds("curl -sS localhost:5050")
       machine.wait_until_succeeds("curl -sS localhost:5050/browser/ | grep \"<title>pgAdmin 4</title>\" > /dev/null")
       machine.succeed("wget -nv --level=1 --spider --recursive localhost:5050/browser")
+
+    with subtest("Check pgadmin minimum password length"):
+      machine2.wait_for_unit("postgresql")
+      machine2.wait_for_console_text("Password must be at least 12 characters long")
   '';
 })
diff --git a/nixos/tests/pgbouncer.nix b/nixos/tests/pgbouncer.nix
index 1e72327d42005..bb5afd35ee28f 100644
--- a/nixos/tests/pgbouncer.nix
+++ b/nixos/tests/pgbouncer.nix
@@ -17,7 +17,8 @@ in
 
       systemd.services.postgresql = {
         postStart = ''
-            ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER ROLE testuser WITH LOGIN PASSWORD 'testpass'";
+          ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER ROLE testuser WITH LOGIN PASSWORD 'testpass'";
+          ${pkgs.postgresql}/bin/psql -U postgres -c "ALTER DATABASE testdb OWNER TO testuser;";
         '';
       };
 
@@ -28,9 +29,6 @@ in
           ensureUsers = [
           {
             name = "testuser";
-            ensurePermissions = {
-              "DATABASE testdb" = "ALL PRIVILEGES";
-            };
           }];
           authentication = ''
             local testdb testuser scram-sha-256
@@ -40,7 +38,7 @@ in
         pgbouncer = {
           enable = true;
           listenAddress = "localhost";
-          databases = { testdb = "host=/run/postgresql/ port=5432 auth_user=testuser dbname=testdb"; };
+          databases = { test = "host=/run/postgresql/ port=5432 auth_user=testuser dbname=testdb"; };
           authType = "scram-sha-256";
           authFile = testAuthFile;
         };
@@ -55,7 +53,7 @@ in
 
     # Test if we can make a query through PgBouncer
     one.wait_until_succeeds(
-        "psql 'postgres://testuser:testpass@localhost:6432/testdb' -c 'SELECT 1;'"
+        "psql 'postgres://testuser:testpass@localhost:6432/test' -c 'SELECT 1;'"
     )
   '';
 })
diff --git a/nixos/tests/pgjwt.nix b/nixos/tests/pgjwt.nix
index 4793a3e315031..8d3310b74eb3b 100644
--- a/nixos/tests/pgjwt.nix
+++ b/nixos/tests/pgjwt.nix
@@ -11,7 +11,7 @@ with pkgs; {
     {
       services.postgresql = {
         enable = true;
-        extraPlugins = [ pgjwt pgtap ];
+        extraPlugins = ps: with ps; [ pgjwt pgtap ];
       };
     };
   };
diff --git a/nixos/tests/plantuml-server.nix b/nixos/tests/plantuml-server.nix
new file mode 100644
index 0000000000000..460c30919aecf
--- /dev/null
+++ b/nixos/tests/plantuml-server.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "plantuml-server";
+  meta.maintainers = with lib.maintainers; [ anthonyroussel ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.curl ];
+    services.plantuml-server.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("plantuml-server.service")
+    machine.wait_for_open_port(8080)
+
+    with subtest("Generate chart"):
+      chart_id = machine.succeed("curl -sSf http://localhost:8080/plantuml/coder -d 'Alice -> Bob'")
+      machine.succeed("curl -sSf http://localhost:8080/plantuml/txt/{}".format(chart_id))
+  '';
+})
diff --git a/nixos/tests/plausible.nix b/nixos/tests/plausible.nix
index ab91e08beb349..9c26c509a5ab5 100644
--- a/nixos/tests/plausible.nix
+++ b/nixos/tests/plausible.nix
@@ -1,16 +1,13 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "plausible";
   meta = with lib.maintainers; {
-    maintainers = [ ma27 ];
+    maintainers = [ ];
   };
 
   nodes.machine = { pkgs, ... }: {
     virtualisation.memorySize = 4096;
     services.plausible = {
       enable = true;
-      releaseCookiePath = "${pkgs.runCommand "cookie" { } ''
-        ${pkgs.openssl}/bin/openssl rand -base64 64 >"$out"
-      ''}";
       adminUser = {
         email = "admin@example.org";
         passwordFile = "${pkgs.writeText "pwd" "foobar"}";
@@ -28,8 +25,14 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     machine.wait_for_unit("plausible.service")
     machine.wait_for_open_port(8000)
 
+    # Ensure that the software does not make not make the machine
+    # listen on any public interfaces by default.
+    machine.fail("ss -tlpn 'src = 0.0.0.0 or src = [::]' | grep LISTEN")
+
     machine.succeed("curl -f localhost:8000 >&2")
 
+    machine.succeed("curl -f localhost:8000/js/script.js >&2")
+
     csrf_token = machine.succeed(
         "curl -c /tmp/cookies localhost:8000/login | grep '_csrf_token' | sed -E 's,.*value=\"(.*)\".*,\\1,g'"
     )
diff --git a/nixos/tests/pleroma.nix b/nixos/tests/pleroma.nix
index 4f1aef854146e..08a01585f8778 100644
--- a/nixos/tests/pleroma.nix
+++ b/nixos/tests/pleroma.nix
@@ -164,9 +164,12 @@ import ./make-test-python.nix ({ pkgs, ... }:
   '';
 
   tls-cert = pkgs.runCommand "selfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
-    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=pleroma.nixos.test' -days 36500
     mkdir -p $out
-    cp key.pem cert.pem $out
+    openssl req -x509 \
+      -subj '/CN=pleroma.nixos.test/' -days 49710 \
+      -addext 'subjectAltName = DNS:pleroma.nixos.test' \
+      -keyout "$out/key.pem" -newkey ed25519 \
+      -out "$out/cert.pem" -noenc
   '';
 
   hosts = nodes: ''
@@ -180,7 +183,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
       security.pki.certificateFiles = [ "${tls-cert}/cert.pem" ];
       networking.extraHosts = hosts nodes;
       environment.systemPackages = with pkgs; [
-        toot
+        pkgs.toot
         send-toot
       ];
     };
diff --git a/nixos/tests/podman/default.nix b/nixos/tests/podman/default.nix
index 0e1f420f2a7de..3eea45832f0a6 100644
--- a/nixos/tests/podman/default.nix
+++ b/nixos/tests/podman/default.nix
@@ -24,8 +24,6 @@ import ../make-test-python.nix (
         virtualisation.podman.enable = true;
 
         virtualisation.podman.defaultNetwork.settings.dns_enabled = true;
-
-        networking.firewall.allowedUDPPorts = [ 53 ];
       };
       docker = { pkgs, ... }: {
         virtualisation.podman.enable = true;
diff --git a/nixos/tests/postgis.nix b/nixos/tests/postgis.nix
index 9d81ebaad85f2..dacf4e576c071 100644
--- a/nixos/tests/postgis.nix
+++ b/nixos/tests/postgis.nix
@@ -9,10 +9,10 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       { pkgs, ... }:
 
       {
-        services.postgresql = let mypg = pkgs.postgresql_11; in {
+        services.postgresql = {
             enable = true;
-            package = mypg;
-            extraPlugins = with mypg.pkgs; [
+            package = pkgs.postgresql;
+            extraPlugins = ps: with ps; [
               postgis
             ];
         };
@@ -24,6 +24,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     master.wait_for_unit("postgresql")
     master.sleep(10)  # Hopefully this is long enough!!
     master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis;'")
+    master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_raster;'")
     master.succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_topology;'")
   '';
 })
diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix
index b44849e0a14e5..c0dd24cf6ad2e 100644
--- a/nixos/tests/postgresql.nix
+++ b/nixos/tests/postgresql.nix
@@ -219,8 +219,6 @@ let
 in
   concatMapAttrs (name: package: {
     ${name} = make-postgresql-test name package false;
+    ${name + "-backup-all"} = make-postgresql-test "${name + "-backup-all"}" package true;
     ${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-admin.nix b/nixos/tests/powerdns-admin.nix
index d7bacb24eec5d..d326d74a98261 100644
--- a/nixos/tests/powerdns-admin.nix
+++ b/nixos/tests/powerdns-admin.nix
@@ -87,9 +87,7 @@ let
           ensureUsers = [
             {
               name = "powerdnsadmin";
-              ensurePermissions = {
-                "DATABASE powerdnsadmin" = "ALL PRIVILEGES";
-              };
+              ensureDBOwnership = true;
             }
           ];
         };
diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix
index 42183625c7c93..51d5e8ae59b92 100644
--- a/nixos/tests/predictable-interface-names.nix
+++ b/nixos/tests/predictable-interface-names.nix
@@ -36,7 +36,7 @@ in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd, systemdStage
       networking.useDHCP = !withNetworkd;
 
       # Check if predictable interface names are working in stage-1
-      boot.initrd.postDeviceCommands = script;
+      boot.initrd.postDeviceCommands = lib.mkIf (!systemdStage1) script;
 
       boot.initrd.systemd = lib.mkIf systemdStage1 {
         enable = true;
diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix
index 7df042e72e907..29c5d810f215a 100644
--- a/nixos/tests/printing.nix
+++ b/nixos/tests/printing.nix
@@ -19,6 +19,7 @@ import ./make-test-python.nix (
       startWhenNeeded = socket;
       listenAddresses = [ "*:631" ];
       defaultShared = true;
+      openFirewall = true;
       extraConf = ''
         <Location />
           Order allow,deny
@@ -26,7 +27,6 @@ import ./make-test-python.nix (
         </Location>
       '';
     };
-    networking.firewall.allowedTCPPorts = [ 631 ];
     # Add a HP Deskjet printer connected via USB to the server.
     hardware.printers.ensurePrinters = [{
       name = "DeskjetLocal";
diff --git a/nixos/tests/privacyidea.nix b/nixos/tests/privacyidea.nix
deleted file mode 100644
index 401ad72c37b72..0000000000000
--- a/nixos/tests/privacyidea.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-# Miscellaneous small tests that don't warrant their own VM run.
-
-import ./make-test-python.nix ({ pkgs, ...} : rec {
-  name = "privacyidea";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ ];
-  };
-
-  nodes.machine = { ... }: {
-    virtualisation.cores = 2;
-
-    services.privacyidea = {
-      enable = true;
-      secretKey = "$SECRET_KEY";
-      pepper = "$PEPPER";
-      adminPasswordFile = pkgs.writeText "admin-password" "testing";
-      adminEmail = "root@localhost";
-
-      # Don't try this at home!
-      environmentFile = pkgs.writeText "pi-secrets.env" ''
-        SECRET_KEY=testing
-        PEPPER=testing
-      '';
-    };
-    services.nginx = {
-      enable = true;
-      virtualHosts."_".locations."/".extraConfig = ''
-        uwsgi_pass unix:/run/privacyidea/socket;
-      '';
-    };
-  };
-
-  testScript = ''
-    machine.start()
-    machine.wait_for_unit("multi-user.target")
-    machine.succeed("curl --fail http://localhost | grep privacyIDEA")
-    machine.succeed("grep \"SECRET_KEY = 'testing'\" /var/lib/privacyidea/privacyidea.cfg")
-    machine.succeed("grep \"PI_PEPPER = 'testing'\" /var/lib/privacyidea/privacyidea.cfg")
-    machine.succeed(
-        "curl --fail http://localhost/auth -F username=admin -F password=testing | grep token"
-    )
-  '';
-})
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 306c5e071e753..5872b02b609e1 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -257,6 +257,21 @@ let
       '';
     };
 
+    exportarr-sonarr = {
+      nodeName = "exportarr_sonarr";
+      exporterConfig = {
+        enable = true;
+        url = "http://127.0.0.1:8989";
+        # testing for real data is tricky, because the api key can not be preconfigured
+        apiKeyFile = pkgs.writeText "dummy-api-key" "eccff6a992bc2e4b88e46d064b26bb4e";
+      };
+      exporterTest = ''
+        wait_for_unit("prometheus-exportarr-sonarr-exporter.service")
+        wait_for_open_port(9707)
+        succeed("curl -sSf 'http://localhost:9707/metrics")
+      '';
+    };
+
     fastly = {
       exporterConfig = {
         enable = true;
@@ -471,7 +486,7 @@ let
         services.knot = {
           enable = true;
           extraArgs = [ "-v" ];
-          extraConfig = ''
+          settingsFile = pkgs.writeText "knot.conf" ''
             server:
               listen: 127.0.0.1@53
 
@@ -512,7 +527,7 @@ let
         wait_for_unit("knot.service")
         wait_for_unit("prometheus-knot-exporter.service")
         wait_for_open_port(9433)
-        succeed("curl -sSf 'localhost:9433' | grep 'knot_server_zone_count 1.0'")
+        succeed("curl -sSf 'localhost:9433' | grep '2\.019031301'")
       '';
     };
 
@@ -791,6 +806,7 @@ let
     nginx = {
       exporterConfig = {
         enable = true;
+        constLabels = [ "foo=bar" ];
       };
       metricProvider = {
         services.nginx = {
@@ -803,7 +819,7 @@ let
         wait_for_unit("nginx.service")
         wait_for_unit("prometheus-nginx-exporter.service")
         wait_for_open_port(9113)
-        succeed("curl -sSf http://localhost:9113/metrics | grep 'nginx_up 1'")
+        succeed("curl -sSf http://localhost:9113/metrics | grep 'nginx_up{foo=\"bar\"} 1'")
       '';
     };
 
@@ -966,6 +982,36 @@ let
       '';
     };
 
+    pgbouncer = {
+      exporterConfig = {
+        enable = true;
+        connectionStringFile = pkgs.writeText "connection.conf" "postgres://admin:@localhost:6432/pgbouncer?sslmode=disable";
+      };
+
+      metricProvider = {
+        services.postgresql.enable = true;
+        services.pgbouncer = {
+          # https://github.com/prometheus-community/pgbouncer_exporter#pgbouncer-configuration
+          ignoreStartupParameters = "extra_float_digits";
+          enable = true;
+          listenAddress = "*";
+          databases = { postgres = "host=/run/postgresql/ port=5432 auth_user=postgres dbname=postgres"; };
+          authType = "any";
+          maxClientConn = 99;
+        };
+      };
+      exporterTest = ''
+        wait_for_unit("postgresql.service")
+        wait_for_unit("pgbouncer.service")
+        wait_for_unit("prometheus-pgbouncer-exporter.service")
+        wait_for_open_port(9127)
+        succeed("curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_up 1'")
+        succeed(
+            "curl -sSf http://localhost:9127/metrics | grep 'pgbouncer_config_max_client_connections 99'"
+        )
+      '';
+    };
+
     php-fpm = {
       nodeName = "php_fpm";
       exporterConfig = {
@@ -1007,6 +1053,50 @@ let
       '';
     };
 
+    ping = {
+      exporterConfig = {
+        enable = true;
+
+        settings = {
+          targets = [ {
+            "localhost" = {
+              alias = "local machine";
+              env = "prod";
+              type = "domain";
+            };
+          } {
+            "127.0.0.1" = {
+              alias = "local machine";
+              type = "v4";
+            };
+          } {
+            "::1" = {
+              alias = "local machine";
+              type = "v6";
+            };
+          } {
+            "google.com" = {};
+          } ];
+          dns = {};
+          ping = {
+            interval = "2s";
+            timeout = "3s";
+            history-size = 42;
+            payload-size = 56;
+          };
+          log = {
+            level = "warn";
+          };
+        };
+      };
+
+      exporterTest = ''
+        wait_for_unit("prometheus-ping-exporter.service")
+        wait_for_open_port(9427)
+        succeed("curl -sSf http://localhost:9427/metrics | grep 'ping_up{.*} 1'")
+      '';
+    };
+
     postfix = {
       exporterConfig = {
         enable = true;
@@ -1178,6 +1268,44 @@ let
       '';
     };
 
+    sabnzbd = {
+      exporterConfig = {
+        enable = true;
+        servers = [{
+          baseUrl = "http://localhost:8080";
+          apiKeyFile = "/var/sabnzbd-apikey";
+        }];
+      };
+
+      metricProvider = {
+        services.sabnzbd.enable = true;
+
+        # unrar is required for sabnzbd
+        nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (pkgs.lib.getName pkg) [ "unrar" ];
+
+        # extract the generated api key before starting
+        systemd.services.sabnzbd-apikey = {
+          requires = [ "sabnzbd.service" ];
+          after = [ "sabnzbd.service" ];
+          requiredBy = [ "prometheus-sabnzbd-exporter.service" ];
+          before = [ "prometheus-sabnzbd-exporter.service" ];
+          script = ''
+            grep -Po '^api_key = \K.+' /var/lib/sabnzbd/sabnzbd.ini > /var/sabnzbd-apikey
+          '';
+        };
+      };
+
+      exporterTest = ''
+        wait_for_unit("sabnzbd.service")
+        wait_for_unit("prometheus-sabnzbd-exporter.service")
+        wait_for_open_port(8080)
+        wait_for_open_port(9387)
+        wait_until_succeeds(
+            "curl -sSf 'localhost:9387/metrics' | grep 'sabnzbd_queue_size{sabnzbd_instance=\"http://localhost:8080\"} 0.0'"
+        )
+      '';
+    };
+
     scaphandre = {
       exporterConfig = {
         enable = true;
@@ -1250,12 +1378,12 @@ let
         wait_for_open_port(9374)
         wait_until_succeeds(
             "curl -sSf localhost:9374/metrics | grep '{}' | grep -v ' 0$'".format(
-                'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1"} '
+                'smokeping_requests_total{host="127.0.0.1",ip="127.0.0.1",source=""} '
             )
         )
         wait_until_succeeds(
             "curl -sSf localhost:9374/metrics | grep '{}'".format(
-                'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1"}'
+                'smokeping_response_ttl{host="127.0.0.1",ip="127.0.0.1",source=""}'
             )
         )
       '';
@@ -1264,9 +1392,11 @@ let
     snmp = {
       exporterConfig = {
         enable = true;
-        configuration.default = {
-          version = 2;
-          auth.community = "public";
+        configuration = {
+          auths.public_v2 = {
+            community = "public";
+            version = 2;
+          };
         };
       };
       exporterTest = ''
diff --git a/nixos/tests/prometheus.nix b/nixos/tests/prometheus.nix
index a075cfc1f1b72..0111273893775 100644
--- a/nixos/tests/prometheus.nix
+++ b/nixos/tests/prometheus.nix
@@ -3,6 +3,7 @@ let
   queryPort  =  9090;
   minioPort  =  9000;
   pushgwPort =  9091;
+  frontPort  =  9092;
 
   s3 = {
     accessKey = "BKIKJAA5BMMU2RHO6IBB";
@@ -152,10 +153,15 @@ in import ./make-test-python.nix {
       services.thanos.query = {
         enable = true;
         http-address = "0.0.0.0:${toString queryPort}";
-        store.addresses = [
+        endpoints = [
           "prometheus:${toString grpcPort}"
         ];
       };
+      services.thanos.query-frontend = {
+        enable = true;
+        http-address = "0.0.0.0:${toString frontPort}";
+        query-frontend.downstream-url = "http://127.0.0.1:${toString queryPort}";
+      };
     };
 
     store = { pkgs, ... }: {
@@ -178,7 +184,7 @@ in import ./make-test-python.nix {
       services.thanos.query = {
         enable = true;
         http-address = "0.0.0.0:${toString queryPort}";
-        store.addresses = [
+        endpoints = [
           "localhost:${toString grpcPort}"
         ];
       };
@@ -262,6 +268,10 @@ in import ./make-test-python.nix {
     query.wait_for_unit("thanos-query.service")
     wait_for_metric(query)
 
+    # Test Thanos query frontend service
+    query.wait_for_unit("thanos-query-frontend.service")
+    query.succeed("curl -sS http://localhost:${toString frontPort}/-/healthy")
+
     # Test if the Thanos sidecar has correctly uploaded its TSDB to S3, if the
     # Thanos storage service has correctly downloaded it from S3 and if the Thanos
     # query service running on $store can correctly retrieve the metric:
diff --git a/nixos/tests/promscale.nix b/nixos/tests/promscale.nix
index d4825b6d7f551..da18628f2482c 100644
--- a/nixos/tests/promscale.nix
+++ b/nixos/tests/promscale.nix
@@ -27,7 +27,7 @@ let
         services.postgresql = {
           enable = true;
           package = postgresql-package;
-          extraPlugins = with postgresql-package.pkgs; [
+          extraPlugins = ps: with ps; [
             timescaledb
             promscale_extension
           ];
diff --git a/nixos/tests/prowlarr.nix b/nixos/tests/prowlarr.nix
index af669afd57004..663743546459f 100644
--- a/nixos/tests/prowlarr.nix
+++ b/nixos/tests/prowlarr.nix
@@ -11,6 +11,8 @@ import ./make-test-python.nix ({ lib, ... }:
   testScript = ''
     machine.wait_for_unit("prowlarr.service")
     machine.wait_for_open_port(9696)
-    machine.succeed("curl --fail http://localhost:9696/")
+    response = machine.succeed("curl --fail http://localhost:9696/")
+    assert '<title>Prowlarr</title>' in response, "Login page didn't load successfully"
+    machine.succeed("[ -d /var/lib/prowlarr ]")
   '';
 })
diff --git a/nixos/tests/qemu-vm-external-disk-image.nix b/nixos/tests/qemu-vm-external-disk-image.nix
new file mode 100644
index 0000000000000..a229fc5e39633
--- /dev/null
+++ b/nixos/tests/qemu-vm-external-disk-image.nix
@@ -0,0 +1,73 @@
+# Tests that you can boot from an external disk image with the qemu-vm module.
+# "External" here means that the image was not produced within the qemu-vm
+# module and relies on the fileSystems option also set outside the qemu-vm
+# module. Most notably, this tests that you can stop the qemu-vm module from
+# overriding fileSystems with virtualisation.fileSystems so you don't have to
+# replicate the previously set fileSystems in virtualisation.fileSystems.
+
+{ lib, ... }:
+
+let
+  rootFslabel = "external";
+  rootFsDevice = "/dev/disk/by-label/${rootFslabel}";
+
+  externalModule = { config, lib, pkgs, ... }: {
+    boot.loader.systemd-boot.enable = true;
+
+    fileSystems = {
+      "/".device = rootFsDevice;
+    };
+
+    system.build.diskImage = import ../lib/make-disk-image.nix {
+      inherit config lib pkgs;
+      label = rootFslabel;
+      partitionTableType = "efi";
+      format = "qcow2";
+      bootSize = "32M";
+      additionalSpace = "0M";
+      copyChannel = false;
+    };
+  };
+in
+{
+  name = "qemu-vm-external-disk-image";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    virtualisation.directBoot.enable = false;
+    virtualisation.mountHostNixStore = false;
+    virtualisation.useEFIBoot = true;
+
+    # This stops the qemu-vm module from overriding the fileSystems option
+    # with virtualisation.fileSystems.
+    virtualisation.fileSystems = lib.mkForce { };
+
+    imports = [ externalModule ];
+  };
+
+  testScript = { nodes, ... }: ''
+    import os
+    import subprocess
+    import tempfile
+
+    tmp_disk_image = tempfile.NamedTemporaryFile()
+
+    subprocess.run([
+      "${nodes.machine.virtualisation.qemu.package}/bin/qemu-img",
+      "create",
+      "-f",
+      "qcow2",
+      "-b",
+      "${nodes.machine.system.build.diskImage}/nixos.qcow2",
+      "-F",
+      "qcow2",
+      tmp_disk_image.name,
+    ])
+
+    # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
+    os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
+
+    machine.succeed("findmnt --kernel --source ${rootFsDevice} --target /")
+  '';
+}
diff --git a/nixos/tests/qemu-vm-restrictnetwork.nix b/nixos/tests/qemu-vm-restrictnetwork.nix
index 49a105ef10767..49aefcc099bda 100644
--- a/nixos/tests/qemu-vm-restrictnetwork.nix
+++ b/nixos/tests/qemu-vm-restrictnetwork.nix
@@ -21,6 +21,8 @@ import ./make-test-python.nix ({
 
     else:
       start_all()
+      unrestricted.systemctl("start network-online.target")
+      restricted.systemctl("start network-online.target")
       unrestricted.wait_for_unit("network-online.target")
       restricted.wait_for_unit("network-online.target")
 
diff --git a/nixos/tests/qgis.nix b/nixos/tests/qgis.nix
new file mode 100644
index 0000000000000..7706b8c077471
--- /dev/null
+++ b/nixos/tests/qgis.nix
@@ -0,0 +1,30 @@
+import ./make-test-python.nix ({ pkgs, lib, qgisPackage, ... }:
+  let
+    testScript = pkgs.writeTextFile {
+      name = "qgis-test.py";
+      text = (builtins.readFile ../../pkgs/applications/gis/qgis/test.py);
+    };
+  in
+  {
+    name = "qgis";
+    meta = {
+      maintainers = with lib; [ teams.geospatial.members ];
+    };
+
+    nodes = {
+      machine = { pkgs, ... }: {
+        virtualisation.diskSize = 2 * 1024;
+
+        imports = [ ./common/x11.nix ];
+        environment.systemPackages = [ qgisPackage ];
+
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      machine.succeed("${qgisPackage}/bin/qgis --version | grep 'QGIS ${qgisPackage.version}'")
+      machine.succeed("${qgisPackage}/bin/qgis --code ${testScript}")
+    '';
+  })
diff --git a/nixos/tests/quicktun.nix b/nixos/tests/quicktun.nix
new file mode 100644
index 0000000000000..a5a6324571174
--- /dev/null
+++ b/nixos/tests/quicktun.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+{
+  name = "quicktun";
+  meta.maintainers = with lib.maintainers; [ h7x4 ];
+
+  nodes = {
+    machine = { ... }: {
+      services.quicktun."test-tunnel" = {
+        protocol = "raw";
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("quicktun-test-tunnel.service")
+  '';
+})
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 626049e73341f..4111720cf6be8 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -4,6 +4,7 @@ import ./make-test-python.nix (
   let
     remoteRepository = "/root/restic-backup";
     remoteFromFileRepository = "/root/restic-backup-from-file";
+    remoteNoInitRepository = "/root/restic-backup-no-init";
     rcloneRepository = "rclone:local:/root/restic-rclone-backup";
 
     backupPrepareCommand = ''
@@ -21,7 +22,10 @@ import ./make-test-python.nix (
       unpackPhase = "true";
       installPhase = ''
         mkdir $out
-        touch $out/some_file
+        echo some_file > $out/some_file
+        echo some_other_file > $out/some_other_file
+        mkdir $out/a_dir
+        echo a_file > $out/a_dir/a_file
       '';
     };
 
@@ -51,11 +55,21 @@ import ./make-test-python.nix (
               inherit passwordFile paths exclude pruneOpts backupPrepareCommand backupCleanupCommand;
               repository = remoteRepository;
               initialize = true;
+              timerConfig = null; # has no effect here, just checking that it doesn't break the service
             };
             remote-from-file-backup = {
-              inherit passwordFile paths exclude pruneOpts;
+              inherit passwordFile exclude pruneOpts;
               initialize = true;
               repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository;
+              paths = [ "/opt/a_dir" ];
+              dynamicFilesFrom = ''
+                find /opt -mindepth 1 -maxdepth 1 ! -name a_dir # all files in /opt except for a_dir
+              '';
+            };
+            remote-noinit-backup = {
+              inherit passwordFile exclude pruneOpts paths;
+              initialize = false;
+              repository = remoteNoInitRepository;
             };
             rclonebackup = {
               inherit passwordFile paths exclude pruneOpts;
@@ -97,9 +111,9 @@ import ./make-test-python.nix (
       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",
+          "restic-remotebackup snapshots",
+          'restic-remote-from-file-backup snapshots"',
+          "restic-rclonebackup snapshots",
           "grep 'backup.* /opt' /root/fake-restic.log",
       )
       server.succeed(
@@ -107,29 +121,39 @@ import ./make-test-python.nix (
           "cp -rT ${testDir} /opt",
           "touch /opt/excluded_file_1 /opt/excluded_file_2",
           "mkdir -p /root/restic-rclone-backup",
+          "restic-remote-noinit-backup init",
 
           # 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 /root/backupCleanupCommand",
-          '${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+          'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
 
           # test that restoring that snapshot produces the same directory
           "mkdir /tmp/restore-1",
-          "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} restore latest -t /tmp/restore-1",
+          "restic-remotebackup restore latest -t /tmp/restore-1",
           "diff -ru ${testDir} /tmp/restore-1/opt",
 
           # test that remote-from-file-backup produces a snapshot
           "systemctl start restic-backups-remote-from-file-backup.service",
-          '${pkgs.restic}/bin/restic -r ${remoteFromFileRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+          'restic-remote-from-file-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that remote-noinit-backup produces a snapshot
+          "systemctl start restic-backups-remote-noinit-backup.service",
+          'restic-remote-noinit-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+
+          # test that restoring that snapshot produces the same directory
+          "mkdir /tmp/restore-2",
+          "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} restore latest -t /tmp/restore-2",
+          "diff -ru ${testDir} /tmp/restore-2/opt",
 
           # 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"',
+          'restic-rclonebackup 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' /root/fake-restic.log",
+          "grep 'backup' /root/fake-restic.log",
           "grep 'check.* --some-check-option' /root/fake-restic.log",
 
           # test that we can create four snapshots in remotebackup and rclonebackup
@@ -158,12 +182,12 @@ import ./make-test-python.nix (
           "rm /root/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"',
+          'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 4"',
+          'restic-rclonebackup 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"',
+          'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
 
       )
     '';
diff --git a/nixos/tests/rkvm/cert.pem b/nixos/tests/rkvm/cert.pem
new file mode 100644
index 0000000000000..933efe520578b
--- /dev/null
+++ b/nixos/tests/rkvm/cert.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC3jCCAcagAwIBAgIUWW1hb9xdRtxAhA42jkS89goW9LUwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEcmt2bTAeFw0yMzA4MjIxOTI1NDlaFw0zMzA4MTkxOTI1
+NDlaMA8xDTALBgNVBAMMBHJrdm0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQCuBsh0+LDXN4b2o/PJjzuiZ9Yv9Pz1Oho9WRiXtNIuHTRdBCcht/iu3PGF
+ICIX+H3dqQOziGSCTAQGJD2p+1ik8d+boJbpa0oxXuHuomsMAT3mib3GpipQoBLP
+KaEbWEsvQbr3RMx8WOtG4dmRQFzSVVtmAXyM0pNyisd4eUCplyIl9gsRJIvsO/0M
+OkgOZW9XLfKiAWlZoyXEkBmPAshg3EkwQtmwxPA/NgWbAOW3zJKSChxnnGYiuIIu
+R/wJ8OQXHP6boQLQGUhCWBKa1uK1gEBmV3Pj6uK8RzTkQq6/47F5sPa6VfqQYdyl
+TCs9bSqHXZjqMBoiSp22uH6+Lh9RAgMBAAGjMjAwMA8GA1UdEQQIMAaHBAoAAAEw
+HQYDVR0OBBYEFEh9HEsnY3dfNKVyPWDbwfR0qHopMA0GCSqGSIb3DQEBCwUAA4IB
+AQB/r+K20JqegUZ/kepPxIU95YY81aUUoxvLbu4EAgh8o46Fgm75qrTZPg4TaIZa
+wtVejekrF+p3QVf0ErUblh/iCjTZPSzCmKHZt8cc9OwTH7bt3bx7heknzLDyIa5z
+szAL+6241UggQ5n5NUGn5+xZHA7TMe47xAZPaRMlCQ/tp5pWFjH6WSSQSP5t4Ag9
+ObhY+uudFjmWi3QIBTr3iIscbWx7tD8cjus7PzM7+kszSDRV04xb6Ox8JzW9MKIN
+GwgwVgs3zCuyqBmTGnR1og3aMk6VtlyZUYE78uuc+fMBxqoBZ0mykeOp0Tbzgtf7
+gPkYcQ6vonoQhuTXYj/NrY+b
+-----END CERTIFICATE-----
diff --git a/nixos/tests/rkvm/default.nix b/nixos/tests/rkvm/default.nix
new file mode 100644
index 0000000000000..22425948d8bf9
--- /dev/null
+++ b/nixos/tests/rkvm/default.nix
@@ -0,0 +1,104 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+let
+  # Generated with
+  #
+  # nix shell .#rkvm --command "rkvm-certificate-gen --ip-addresses 10.0.0.1 cert.pem key.pem"
+  #
+  snakeoil-cert = ./cert.pem;
+  snakeoil-key = ./key.pem;
+in
+{
+  name = "rkvm";
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ ../common/user-account.nix ];
+
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.1/24";
+      };
+
+      services.getty.autologinUser = "alice";
+
+      services.rkvm.server = {
+        enable = true;
+        settings = {
+          certificate = snakeoil-cert;
+          key = snakeoil-key;
+          password = "snakeoil";
+          switch-keys = [ "left-alt" "right-alt" ];
+        };
+      };
+    };
+
+    client = { pkgs, ... }: {
+      imports = [ ../common/user-account.nix ];
+
+      virtualisation.vlans = [ 1 ];
+
+      networking = {
+        useNetworkd = true;
+        useDHCP = false;
+        firewall.enable = false;
+      };
+
+      systemd.network.networks."01-eth1" = {
+        name = "eth1";
+        networkConfig.Address = "10.0.0.2/24";
+      };
+
+      services.getty.autologinUser = "alice";
+
+      services.rkvm.client = {
+        enable = true;
+        settings = {
+          server = "10.0.0.1:5258";
+          certificate = snakeoil-cert;
+          key = snakeoil-key;
+          password = "snakeoil";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    server.wait_for_unit("getty@tty1.service")
+    server.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    server.wait_for_unit("rkvm-server")
+    server.wait_for_open_port(5258)
+
+    client.wait_for_unit("getty@tty1.service")
+    client.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    client.wait_for_unit("rkvm-client")
+
+    server.sleep(1)
+
+    # Switch to client
+    server.send_key("alt-alt_r", delay=0.2)
+    server.send_chars("echo 'hello client' > /tmp/test.txt\n")
+
+    # Switch to server
+    server.send_key("alt-alt_r", delay=0.2)
+    server.send_chars("echo 'hello server' > /tmp/test.txt\n")
+
+    server.sleep(1)
+
+    client.systemctl("stop rkvm-client.service")
+    server.systemctl("stop rkvm-server.service")
+
+    server_file = server.succeed("cat /tmp/test.txt")
+    assert server_file.strip() == "hello server"
+
+    client_file = client.succeed("cat /tmp/test.txt")
+    assert client_file.strip() == "hello client"
+  '';
+})
diff --git a/nixos/tests/rkvm/key.pem b/nixos/tests/rkvm/key.pem
new file mode 100644
index 0000000000000..7197decff8d3d
--- /dev/null
+++ b/nixos/tests/rkvm/key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCuBsh0+LDXN4b2
+o/PJjzuiZ9Yv9Pz1Oho9WRiXtNIuHTRdBCcht/iu3PGFICIX+H3dqQOziGSCTAQG
+JD2p+1ik8d+boJbpa0oxXuHuomsMAT3mib3GpipQoBLPKaEbWEsvQbr3RMx8WOtG
+4dmRQFzSVVtmAXyM0pNyisd4eUCplyIl9gsRJIvsO/0MOkgOZW9XLfKiAWlZoyXE
+kBmPAshg3EkwQtmwxPA/NgWbAOW3zJKSChxnnGYiuIIuR/wJ8OQXHP6boQLQGUhC
+WBKa1uK1gEBmV3Pj6uK8RzTkQq6/47F5sPa6VfqQYdylTCs9bSqHXZjqMBoiSp22
+uH6+Lh9RAgMBAAECggEABo2V1dBu5E51zsAiFCMdypdLZEyUNphvWC5h3oXowONz
+pH8ICYfXyEnkma/kk2+ALy0dSRDn6/94dVIUX7Fpx0hJCcoJyhSysK+TJWfIonqX
+ffYOMeFG8vicIgs+GFKs/hoPtB5LREbFkUqRj/EoWE6Y3aX3roaCwTZC8vaUk0OK
+54gExcNXRwQtFmfM9BiPT76F2J641NVsddgKumrryMi605CgZ57OFfSYEena6T3t
+JbQ1TKB3SH1LvSQIspyp56E3bjh8bcwSh72g88YxWZI9yarOesmyU+fXnmVqcBc+
+CiJDX3Te1C2GIkBiH3HZJo4P88aXrkJ7J8nub/812QKBgQDfCHjBy5uWzzbDnqZc
+cllIyUqMHq1iY2/btdZQbz83maZhQhH2UL4Zvoa7qgMX7Ou5jn1xpDaMeXNaajGK
+Fz66nmqQEUFX1i+2md2J8TeKD37yUJRdlrMiAc+RNp5wiOH9EI18g2m6h/nj3s/P
+MdNyxsz+wqOiJT0sZatarKiFhQKBgQDHv+lPy4OPH1MeSv5vmv3Pa41O/CeiPy+T
+gi6nEZayVRVog3zF9T6gNIHrZ1fdIppWPiPXv9fmC3s/IVEftLG6YC+MAfigYhiz
+Iceoal0iJJ8DglzOhlKgHEnxEwENCz8aJxjpvbxHHcpvgXdBSEVfHvVqDkAFTsvF
+JA5YTmqGXQKBgQCL6uqm2S7gq1o12p+PO4VbrjwAL3aiVLNl6Gtsxn2oSdIhDavr
+FLhNukMYFA4gwlcXb5au5k/6TG7bd+dgNDj8Jkm/27NcgVgpe9mJojQvfo0rQvXw
+yIvUd8JZ3SQEgTsU4X+Bb4eyp39TPwKrfxyh0qnj4QN6w1XfNmELX2nRaQKBgEq6
+a0ik9JTovSnKGKIcM/QTYow4HYO/a8cdnuJ13BDfb+DnwBg3BbTdr/UndmGOfnrh
+SHuAk/7GMNePWVApQ4xcS61vV1p5GJB7hLxm/my1kp+3d4z0B5lKvAbqeywsFvFr
+yxA3IWbhqEhLARh1Ny684EdLCXxy3Bzmvk8fFw8pAoGAGkt9pJC2wkk9fnJIHq+f
+h/WnEO0YrGzYnVA+RyCNKrimRd+GylGHJ/Ev6PRZvMwyGE7RCB+fHVrrEcEJAcxL
+SaOg5NA8cwrG+UpTQqi4gt6tCW87afVCyL6dC/E8giJlzI0LY9DnFGoVqYL0qJvm
+Sj4SU0fyLsW/csOLd5T+Bf8=
+-----END PRIVATE KEY-----
diff --git a/nixos/tests/rosenpass.nix b/nixos/tests/rosenpass.nix
new file mode 100644
index 0000000000000..ec4046c8c035b
--- /dev/null
+++ b/nixos/tests/rosenpass.nix
@@ -0,0 +1,217 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  deviceName = "rp0";
+
+  server = {
+    ip = "fe80::1";
+    wg = {
+      public = "mQufmDFeQQuU/fIaB2hHgluhjjm1ypK4hJr1cW3WqAw=";
+      secret = "4N5Y1dldqrpsbaEiY8O0XBUGUFf8vkvtBtm8AoOX7Eo=";
+      listen = 10000;
+    };
+  };
+  client = {
+    ip = "fe80::2";
+    wg = {
+      public = "Mb3GOlT7oS+F3JntVKiaD7SpHxLxNdtEmWz/9FMnRFU=";
+      secret = "uC5dfGMv7Oxf5UDfdPkj6rZiRZT2dRWp5x8IQxrNcUE=";
+    };
+  };
+in
+{
+  name = "rosenpass";
+
+  nodes =
+    let
+      shared = peer: { config, modulesPath, ... }: {
+        imports = [ "${modulesPath}/services/networking/rosenpass.nix" ];
+
+        boot.kernelModules = [ "wireguard" ];
+
+        services.rosenpass = {
+          enable = true;
+          defaultDevice = deviceName;
+          settings = {
+            verbosity = "Verbose";
+            public_key = "/etc/rosenpass/pqpk";
+            secret_key = "/etc/rosenpass/pqsk";
+          };
+        };
+
+        networking.firewall.allowedUDPPorts = [ 9999 ];
+
+        systemd.network = {
+          enable = true;
+          networks."rosenpass" = {
+            matchConfig.Name = deviceName;
+            networkConfig.IPForward = true;
+            address = [ "${peer.ip}/64" ];
+          };
+
+          netdevs."10-rp0" = {
+            netdevConfig = {
+              Kind = "wireguard";
+              Name = deviceName;
+            };
+            wireguardConfig.PrivateKeyFile = "/etc/wireguard/wgsk";
+          };
+        };
+
+        environment.etc."wireguard/wgsk" = {
+          text = peer.wg.secret;
+          user = "systemd-network";
+          group = "systemd-network";
+        };
+      };
+    in
+    {
+      server = {
+        imports = [ (shared server) ];
+
+        networking.firewall.allowedUDPPorts = [ server.wg.listen ];
+
+        systemd.network.netdevs."10-${deviceName}" = {
+          wireguardConfig.ListenPort = server.wg.listen;
+          wireguardPeers = [
+            {
+              wireguardPeerConfig = {
+                AllowedIPs = [ "::/0" ];
+                PublicKey = client.wg.public;
+              };
+            }
+          ];
+        };
+
+        services.rosenpass.settings = {
+          listen = [ "0.0.0.0:9999" ];
+          peers = [
+            {
+              public_key = "/etc/rosenpass/peers/client/pqpk";
+              peer = client.wg.public;
+            }
+          ];
+        };
+      };
+      client = {
+        imports = [ (shared client) ];
+
+        systemd.network.netdevs."10-${deviceName}".wireguardPeers = [
+          {
+            wireguardPeerConfig = {
+              AllowedIPs = [ "::/0" ];
+              PublicKey = server.wg.public;
+              Endpoint = "server:${builtins.toString server.wg.listen}";
+            };
+          }
+        ];
+
+        services.rosenpass.settings.peers = [
+          {
+            public_key = "/etc/rosenpass/peers/server/pqpk";
+            endpoint = "server:9999";
+            peer = server.wg.public;
+          }
+        ];
+      };
+    };
+
+  testScript = { ... }: ''
+    from os import system
+
+    # Full path to rosenpass in the store, to avoid fiddling with `$PATH`.
+    rosenpass = "${pkgs.rosenpass}/bin/rosenpass"
+
+    # Path in `/etc` where keys will be placed.
+    etc = "/etc/rosenpass"
+
+    start_all()
+
+    for machine in [server, client]:
+        machine.wait_for_unit("multi-user.target")
+
+    # Gently stop Rosenpass to avoid crashes during key generation/distribution.
+    for machine in [server, client]:
+        machine.execute("systemctl stop rosenpass.service")
+
+    for (name, machine, remote) in [("server", server, client), ("client", client, server)]:
+        pk, sk = f"{name}.pqpk", f"{name}.pqsk"
+        system(f"{rosenpass} gen-keys --force --secret-key {sk} --public-key {pk}")
+        machine.copy_from_host(sk, f"{etc}/pqsk")
+        machine.copy_from_host(pk, f"{etc}/pqpk")
+        remote.copy_from_host(pk, f"{etc}/peers/{name}/pqpk")
+
+    for machine in [server, client]:
+        machine.execute("systemctl start rosenpass.service")
+
+    for machine in [server, client]:
+        machine.wait_for_unit("rosenpass.service")
+
+    with subtest("ping"):
+        client.succeed("ping -c 2 -i 0.5 ${server.ip}%${deviceName}")
+
+    with subtest("preshared-keys"):
+        # Rosenpass works by setting the WireGuard preshared key at regular intervals.
+        # Thus, if it is not active, then no key will be set, and the output of `wg show` will contain "none".
+        # Otherwise, if it is active, then the key will be set and "none" will not be found in the output of `wg show`.
+        for machine in [server, client]:
+            machine.wait_until_succeeds("wg show all preshared-keys | grep --invert-match none", timeout=5)
+  '';
+
+  # NOTE: Below configuration is for "interactive" (=developing/debugging) only.
+  interactive.nodes =
+    let
+      inherit (import ./ssh-keys.nix pkgs) snakeOilPublicKey snakeOilPrivateKey;
+
+      sshAndKeyGeneration = {
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+        environment.systemPackages = [
+          (pkgs.writeShellApplication {
+            name = "gen-keys";
+            runtimeInputs = [ pkgs.rosenpass ];
+            text = ''
+              HOST="$(hostname)"
+              if [ "$HOST" == "server" ]
+              then
+                PEER="client"
+              else
+                PEER="server"
+              fi
+
+              # Generate keypair.
+              mkdir -vp /etc/rosenpass/peers/$PEER
+              rosenpass gen-keys --force --secret-key /etc/rosenpass/pqsk --public-key /etc/rosenpass/pqpk
+
+              # Set up SSH key.
+              mkdir -p /root/.ssh
+              cp ${snakeOilPrivateKey} /root/.ssh/id_ecdsa
+              chmod 0400 /root/.ssh/id_ecdsa
+
+              # Copy public key to other peer.
+              # shellcheck disable=SC2029
+              ssh -o StrictHostKeyChecking=no $PEER "mkdir -pv /etc/rosenpass/peers/$HOST"
+              scp /etc/rosenpass/pqpk "$PEER:/etc/rosenpass/peers/$HOST/pqpk"
+            '';
+          })
+        ];
+      };
+
+      # Use kmscon <https://www.freedesktop.org/wiki/Software/kmscon/>
+      # to provide a slightly nicer console, and while we're at it,
+      # also use a nice font.
+      # With kmscon, we can for example zoom in/out using [Ctrl] + [+]
+      # and [Ctrl] + [-]
+      niceConsoleAndAutologin.services.kmscon = {
+        enable = true;
+        autologinUser = "root";
+        fonts = [{
+          name = "Fira Code";
+          package = pkgs.fira-code;
+        }];
+      };
+    in
+    {
+      server = sshAndKeyGeneration // niceConsoleAndAutologin;
+      client = sshAndKeyGeneration // niceConsoleAndAutologin;
+    };
+})
diff --git a/nixos/tests/rspamd-trainer.nix b/nixos/tests/rspamd-trainer.nix
new file mode 100644
index 0000000000000..9c157903d24b6
--- /dev/null
+++ b/nixos/tests/rspamd-trainer.nix
@@ -0,0 +1,155 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+let
+  certs = import ./common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in {
+  name = "rspamd-trainer";
+  meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
+
+  nodes = {
+    machine = { options, config, ... }: {
+
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+
+      networking.extraHosts = ''
+        127.0.0.1 ${domain}
+     '';
+
+      services.rspamd-trainer = {
+        enable = true;
+        settings = {
+          HOST = domain;
+          USERNAME = "spam@${domain}";
+          INBOXPREFIX = "INBOX/";
+        };
+        secrets = [
+          # Do not use this in production. This will make passwords
+          # world-readable in the Nix store
+          "${pkgs.writeText "secrets" ''
+            PASSWORD = test123
+          ''}"
+        ];
+      };
+
+      services.maddy = {
+        enable = true;
+        hostname = domain;
+        primaryDomain = domain;
+        ensureAccounts = [ "spam@${domain}" ];
+        ensureCredentials = {
+          # Do not use this in production. This will make passwords world-readable
+          # in the Nix store
+          "spam@${domain}".passwordFile = "${pkgs.writeText "postmaster" "test123"}";
+        };
+        tls = {
+          loader = "file";
+          certificates = [{
+            certPath = "${certs.${domain}.cert}";
+            keyPath = "${certs.${domain}.key}";
+          }];
+        };
+        config = builtins.replaceStrings [
+          "imap tcp://0.0.0.0:143"
+          "submission tcp://0.0.0.0:587"
+        ] [
+          "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
+          "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
+        ] options.services.maddy.config.default;
+      };
+
+      services.rspamd = {
+        enable = true;
+        locals = {
+          "redis.conf".text = ''
+            servers = "${config.services.redis.servers.rspamd.unixSocket}";
+          '';
+          "classifier-bayes.conf".text = ''
+            backend = "redis";
+            autolearn = true;
+          '';
+        };
+      };
+
+      services.redis.servers.rspamd = {
+        enable = true;
+        port = 0;
+        unixSocket = "/run/redis-rspamd/redis.sock";
+        user = config.services.rspamd.user;
+      };
+
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "send-testmail" { } ''
+          import smtplib
+          import ssl
+          from email.mime.text import MIMEText
+          context = ssl.create_default_context()
+          msg = MIMEText("Hello World")
+          msg['Subject'] = 'Test'
+          msg['From'] = "spam@${domain}"
+          msg['To'] = "spam@${domain}"
+          with smtplib.SMTP_SSL(host='${domain}', port=465, context=context) as smtp:
+              smtp.login('spam@${domain}', 'test123')
+              smtp.sendmail(
+                'spam@${domain}', 'spam@${domain}', msg.as_string()
+              )
+        '')
+        (pkgs.writers.writePython3Bin "create-mail-dirs" { } ''
+          import imaplib
+          with imaplib.IMAP4_SSL('${domain}') as imap:
+              imap.login('spam@${domain}', 'test123')
+              imap.create("\"INBOX/report_spam\"")
+              imap.create("\"INBOX/report_ham\"")
+              imap.create("\"INBOX/report_spam_reply\"")
+              imap.select("INBOX")
+              imap.copy("1", "\"INBOX/report_ham\"")
+              imap.logout()
+        '')
+        (pkgs.writers.writePython3Bin "test-imap" { } ''
+          import imaplib
+          with imaplib.IMAP4_SSL('${domain}') as imap:
+              imap.login('spam@${domain}', 'test123')
+              imap.select("INBOX/learned_ham")
+              status, refs = imap.search(None, 'ALL')
+              assert status == 'OK'
+              assert len(refs) == 1
+              status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+              assert status == 'OK'
+              assert msg[0][1].strip() == b"Hello World"
+              imap.logout()
+        '')
+      ];
+
+
+
+    };
+
+  };
+
+  testScript = { nodes }: ''
+    start_all()
+    machine.wait_for_unit("maddy.service")
+    machine.wait_for_open_port(143)
+    machine.wait_for_open_port(993)
+    machine.wait_for_open_port(587)
+    machine.wait_for_open_port(465)
+
+    # Send test mail to spam@domain
+    machine.succeed("send-testmail")
+
+    # Create mail directories required for rspamd-trainer and copy mail from
+    # INBOX into INBOX/report_ham
+    machine.succeed("create-mail-dirs")
+
+    # Start rspamd-trainer. It should read mail from INBOX/report_ham
+    machine.wait_for_unit("rspamd.service")
+    machine.wait_for_unit("redis-rspamd.service")
+    machine.wait_for_file("/run/rspamd/rspamd.sock")
+    machine.succeed("systemctl start rspamd-trainer.service")
+
+    # Check if mail got processed by rspamd-trainer successfully and check for
+    # it in INBOX/learned_ham
+    machine.succeed("test-imap")
+  '';
+})
diff --git a/nixos/tests/rss2email.nix b/nixos/tests/rss2email.nix
index f32326feb50fb..60b27b95fabe4 100644
--- a/nixos/tests/rss2email.nix
+++ b/nixos/tests/rss2email.nix
@@ -55,6 +55,7 @@ import ./make-test-python.nix {
   testScript = ''
     start_all()
 
+    server.systemctl("start network-online.target")
     server.wait_for_unit("network-online.target")
     server.wait_for_unit("opensmtpd")
     server.wait_for_unit("dovecot2")
diff --git a/nixos/tests/sabnzbd.nix b/nixos/tests/sabnzbd.nix
index 075bd0b1fe093..64cb655b43157 100644
--- a/nixos/tests/sabnzbd.nix
+++ b/nixos/tests/sabnzbd.nix
@@ -18,5 +18,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     machine.wait_until_succeeds(
         "curl --fail -L http://localhost:8080/"
     )
+    _, out = machine.execute("grep SABCTools /var/lib/sabnzbd/logs/sabnzbd.log")
+    machine.log(out)
+    machine.fail("grep 'SABCTools disabled: no correct version found!' /var/lib/sabnzbd/logs/sabnzbd.log")
   '';
 })
diff --git a/nixos/tests/seatd.nix b/nixos/tests/seatd.nix
new file mode 100644
index 0000000000000..138a6cb1cf44c
--- /dev/null
+++ b/nixos/tests/seatd.nix
@@ -0,0 +1,51 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  seatd-test = pkgs.writeShellApplication {
+    name = "seatd-client-pid";
+    text = ''
+      journalctl -u seatd --no-pager -b | while read -r line; do
+          case "$line" in
+          *"New client connected"*)
+              line="''${line##*pid: }"
+              pid="''${line%%,*}"
+              ;;
+          *"Opened client"*)
+              echo "$pid"
+              exit
+          esac
+      done;
+    '';
+  };
+in
+{
+  name = "seatd";
+  meta.maintainers = with lib.maintainers; [ sinanmohd ];
+
+  nodes.machine = { ... }: {
+    imports = [ ./common/user-account.nix ];
+    services.getty.autologinUser = "alice";
+    users.users.alice.extraGroups = [ "seat" "wheel" ];
+
+    fonts.enableDefaultPackages = true;
+    environment.systemPackages = with pkgs; [
+      dwl
+      foot
+      seatd-test
+    ];
+
+    programs.bash.loginShellInit = ''
+      [ "$(tty)" = "/dev/tty1" ] &&
+          dwl -s 'foot touch /tmp/foot_started'
+    '';
+
+    hardware.opengl.enable = true;
+    virtualisation.qemu.options = [ "-vga none -device virtio-gpu-pci" ];
+    services.seatd.enable = true;
+  };
+
+  testScript = ''
+    machine.wait_for_file("/tmp/foot_started")
+    machine.succeed("test $(seatd-client-pid) = $(pgrep dwl)")
+  '';
+})
diff --git a/nixos/tests/sftpgo.nix b/nixos/tests/sftpgo.nix
index 8cd5675c1d4d4..a5bb1981d2c3c 100644
--- a/nixos/tests/sftpgo.nix
+++ b/nixos/tests/sftpgo.nix
@@ -17,7 +17,7 @@ let
 
   # Returns an attributeset of users who are not system users.
   normalUsers = config:
-    filterAttrs (name: user: user.isNormalUser) config.users.users;
+    lib.filterAttrs (name: user: user.isNormalUser) config.users.users;
 
   # Returns true if a user is a member of the given group
   isMemberOf =
@@ -26,7 +26,7 @@ let
     groupName:
     # users.users attrset
     user:
-      any (x: x == user.name) config.users.groups.${groupName}.members;
+      lib.any (x: x == user.name) config.users.groups.${groupName}.members;
 
   # Generates a valid SFTPGo user configuration for a given user
   # Will be converted to JSON and loaded on application startup.
@@ -52,7 +52,7 @@ let
       # inside the dataprovider they will be automatically created.
       # You have to create the folder on the filesystem yourself
       virtual_folders =
-        lib.optional (lib.isMemberOf config sharedFolderName user) {
+        lib.optional (isMemberOf config sharedFolderName user) {
           name = sharedFolderName;
           mapped_path = "${config.services.sftpgo.dataDir}/${sharedFolderName}";
           virtual_path = "/${sharedFolderName}";
@@ -63,7 +63,7 @@ let
         lib.recursiveUpdate {
           "/" = [ "list" ];     # read-only top level directory
           "/private" = [ "*" ]; # private subdirectory, not shared with others
-        } (lib.optionalAttrs (lib.isMemberOf config "shared" user) {
+        } (lib.optionalAttrs (isMemberOf config "shared" user) {
           "/shared" = [ "*" ];
         });
 
@@ -89,7 +89,7 @@ let
   # of users and folders to import to SFTPGo.
   loadDataJson = config: pkgs.writeText "users-and-folders.json" (builtins.toJSON {
     users =
-      lib.mapAttrsToList (name: user: lib.generateUserAttrSet config user) (normalUsers config);
+      lib.mapAttrsToList (name: user: generateUserAttrSet config user) (normalUsers config);
 
     folders = [
       {
@@ -144,7 +144,7 @@ in
 {
   name = "sftpgo";
 
-  meta.maintainers = with maintainers; [ yayayayaka ];
+  meta.maintainers = with lib.maintainers; [ yayayayaka ];
 
   nodes = {
     server = { nodes, ... }: {
@@ -156,7 +156,7 @@ in
         ensureDatabases = [ "sftpgo" ];
         ensureUsers = [{
           name = "sftpgo";
-          ensurePermissions."DATABASE sftpgo" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }];
       };
 
@@ -228,7 +228,7 @@ in
           # Created shared folder directories
           "d ${statePath}/${sharedFolderName} 2770 ${sftpgoUser} ${sharedFolderName}   -"
         ]
-        ++ mapAttrsToList (name: user:
+        ++ lib.mapAttrsToList (name: user:
           # Create private user directories
           ''
             d ${statePath}/users/${user.name} 0700 ${sftpgoUser} ${sftpgoGroup} -
@@ -273,12 +273,12 @@ in
           networking.firewall.allowedTCPPorts = [ 22 80 ];
           services.sftpgo = {
             settings = {
-              sftpd.bindings = mkForce [{
+              sftpd.bindings = lib.mkForce [{
                 address = "";
                 port = 22;
               }];
 
-              httpd.bindings = mkForce [{
+              httpd.bindings = lib.mkForce [{
                 address = "";
                 port = 80;
               }];
diff --git a/nixos/tests/sgtpuzzles.nix b/nixos/tests/sgt-puzzles.nix
index b8d25d42d312f..4c5210bfce778 100644
--- a/nixos/tests/sgtpuzzles.nix
+++ b/nixos/tests/sgt-puzzles.nix
@@ -1,6 +1,6 @@
 import ./make-test-python.nix ({ pkgs, ...} :
 {
-  name = "sgtpuzzles";
+  name = "sgt-puzzles";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ tomfitzhenry ];
   };
@@ -14,7 +14,7 @@ import ./make-test-python.nix ({ pkgs, ...} :
 
     services.xserver.enable = true;
     environment.systemPackages = with pkgs; [
-      sgtpuzzles
+      sgt-puzzles
     ];
   };
 
diff --git a/nixos/tests/shadow.nix b/nixos/tests/shadow.nix
index c9a04088e8709..a027af7e450b5 100644
--- a/nixos/tests/shadow.nix
+++ b/nixos/tests/shadow.nix
@@ -32,7 +32,7 @@ in import ./make-test-python.nix ({ pkgs, ... }: {
       };
       users.berta = {
         isNormalUser = true;
-        hashedPassword = hashed_bcrypt;
+        hashedPasswordFile = (pkgs.writeText "hashed_bcrypt" hashed_bcrypt).outPath;
         shell = pkgs.bash;
       };
       users.yesim = {
diff --git a/nixos/tests/shattered-pixel-dungeon.nix b/nixos/tests/shattered-pixel-dungeon.nix
index a256bbdfd7357..b4ac1670b5cad 100644
--- a/nixos/tests/shattered-pixel-dungeon.nix
+++ b/nixos/tests/shattered-pixel-dungeon.nix
@@ -21,9 +21,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       machine.wait_for_x()
       machine.execute("shattered-pixel-dungeon >&2 &")
       machine.wait_for_window(r"Shattered Pixel Dungeon")
-      machine.sleep(5)
-      if "Enter" not in machine.get_screen_text():
-          raise Exception("Program did not start successfully")
+      machine.wait_for_text("Enter")
       machine.screenshot("screen")
     '';
 })
diff --git a/nixos/tests/slimserver.nix b/nixos/tests/slimserver.nix
new file mode 100644
index 0000000000000..95cbdcf4a2a15
--- /dev/null
+++ b/nixos/tests/slimserver.nix
@@ -0,0 +1,47 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "slimserver";
+  meta.maintainers = with pkgs.lib.maintainers; [ adamcstephens ];
+
+  nodes.machine = { ... }: {
+    services.slimserver.enable = true;
+    services.squeezelite = {
+      enable = true;
+      extraArguments = "-s 127.0.0.1 -d slimproto=info";
+    };
+    sound.enable = true;
+    boot.initrd.kernelModules = ["snd-dummy"];
+  };
+
+  testScript =
+    ''
+      import json
+      rpc_get_player = {
+          "id": 1,
+          "method": "slim.request",
+          "params":[0,["player", "id", "0", "?"]]
+      }
+
+      with subtest("slimserver is started"):
+          machine.wait_for_unit("slimserver.service")
+          # give slimserver a moment to report errors
+          machine.sleep(2)
+
+      with subtest('slimserver module errors are not reported'):
+          machine.fail("journalctl -u slimserver.service | grep 'throw_exception'")
+          machine.fail("journalctl -u slimserver.service | grep 'not installed'")
+          machine.fail("journalctl -u slimserver.service | grep 'not found'")
+          machine.fail("journalctl -u slimserver.service | grep 'The following CPAN modules were found but cannot work with Logitech Media Server'")
+          machine.fail("journalctl -u slimserver.service | grep 'please use the buildme.sh'")
+
+      with subtest('slimserver is ready'):
+          machine.wait_for_open_port(9000)
+          machine.wait_until_succeeds("journalctl -u slimserver.service | grep 'Completed dbOptimize Scan'")
+
+      with subtest("squeezelite player successfully connects to slimserver"):
+          machine.wait_for_unit("squeezelite.service")
+          machine.wait_until_succeeds("journalctl -u squeezelite.service | grep -E 'slimproto:[0-9]+ connected'")
+          player_mac = machine.wait_until_succeeds("journalctl -eu squeezelite.service | grep -E 'sendHELO:[0-9]+ mac:'").strip().split(" ")[-1]
+          player_id = machine.succeed(f"curl http://localhost:9000/jsonrpc.js -g -X POST -d '{json.dumps(rpc_get_player)}'")
+          assert player_mac == json.loads(player_id)["result"]["_id"], "squeezelite player not found"
+    '';
+})
diff --git a/nixos/tests/snmpd.nix b/nixos/tests/snmpd.nix
new file mode 100644
index 0000000000000..9248a6b390101
--- /dev/null
+++ b/nixos/tests/snmpd.nix
@@ -0,0 +1,23 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "snmpd";
+
+  nodes.snmpd = {
+    environment.systemPackages = with pkgs; [
+      net-snmp
+    ];
+
+    services.snmpd = {
+      enable = true;
+      configText = ''
+        rocommunity public
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all();
+    machine.wait_for_unit("snmpd.service")
+    machine.succeed("snmpwalk -v 2c -c public localhost | grep SNMPv2-MIB::sysName.0");
+  '';
+
+})
diff --git a/nixos/tests/soft-serve.nix b/nixos/tests/soft-serve.nix
new file mode 100644
index 0000000000000..1c4cb4c95819e
--- /dev/null
+++ b/nixos/tests/soft-serve.nix
@@ -0,0 +1,102 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+let
+  inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+  sshPort = 8231;
+  httpPort = 8232;
+  statsPort = 8233;
+  gitPort = 8418;
+in
+{
+  name = "soft-serve";
+  meta.maintainers = with lib.maintainers; [ dadada ];
+  nodes = {
+    client = { pkgs, ... }: {
+      environment.systemPackages = with pkgs; [
+        curl
+        git
+        openssh
+      ];
+      environment.etc.sshKey = {
+        source = snakeOilPrivateKey;
+        mode = "0600";
+      };
+    };
+
+    server =
+      { config, ... }:
+      {
+        services.soft-serve = {
+          enable = true;
+          settings = {
+            name = "TestServer";
+            ssh.listen_addr = ":${toString sshPort}";
+            git.listen_addr = ":${toString gitPort}";
+            http.listen_addr = ":${toString httpPort}";
+            stats.listen_addr = ":${toString statsPort}";
+            initial_admin_keys = [ snakeOilPublicKey ];
+          };
+        };
+        networking.firewall.allowedTCPPorts = [ sshPort httpPort statsPort ];
+      };
+  };
+
+  testScript =
+    { ... }:
+    ''
+      SSH_PORT = ${toString sshPort}
+      HTTP_PORT = ${toString httpPort}
+      STATS_PORT = ${toString statsPort}
+      KEY = "${snakeOilPublicKey}"
+      SSH_KEY = "/etc/sshKey"
+      SSH_COMMAND = f"ssh -p {SSH_PORT} -i {SSH_KEY} -o StrictHostKeyChecking=no"
+      TEST_DIR = "/tmp/test"
+      GIT = f"git -C {TEST_DIR}"
+
+      for machine in client, server:
+          machine.wait_for_unit("network.target")
+
+      server.wait_for_unit("soft-serve.service")
+      server.wait_for_open_port(SSH_PORT)
+
+      with subtest("Get info"):
+          status, test = client.execute(f"{SSH_COMMAND} server info")
+          if status != 0:
+              raise Exception("Failed to get SSH info")
+          key = " ".join(KEY.split(" ")[0:2])
+          if not key in test:
+              raise Exception("Admin key must be configured correctly")
+
+      with subtest("Create user"):
+          client.succeed(f"{SSH_COMMAND} server user create beatrice")
+          client.succeed(f"{SSH_COMMAND} server user info beatrice")
+
+      with subtest("Create repo"):
+          client.succeed(f"git init {TEST_DIR}")
+          client.succeed(f"{GIT} config --global user.email you@example.com")
+          client.succeed(f"touch {TEST_DIR}/foo")
+          client.succeed(f"{GIT} add foo")
+          client.succeed(f"{GIT} commit --allow-empty -m test")
+          client.succeed(f"{GIT} remote add origin git@server:test")
+          client.succeed(f"GIT_SSH_COMMAND='{SSH_COMMAND}' {GIT} push -u origin master")
+          client.execute("rm -r /tmp/test")
+
+      server.wait_for_open_port(HTTP_PORT)
+
+      with subtest("Clone over HTTP"):
+          client.succeed(f"curl --connect-timeout 10 http://server:{HTTP_PORT}/")
+          client.succeed(f"git clone http://server:{HTTP_PORT}/test /tmp/test")
+          client.execute("rm -r /tmp/test")
+
+      with subtest("Clone over SSH"):
+          client.succeed(f"GIT_SSH_COMMAND='{SSH_COMMAND}' git clone git@server:test /tmp/test")
+          client.execute("rm -r /tmp/test")
+
+      with subtest("Get stats over HTTP"):
+          server.wait_for_open_port(STATS_PORT)
+          status, test = client.execute(f"curl --connect-timeout 10 http://server:{STATS_PORT}/metrics")
+          if status != 0:
+              raise Exception("Failed to get metrics from status port")
+          if not "go_gc_duration_seconds_count" in test:
+              raise Exception("Metrics did not contain key 'go_gc_duration_seconds_count'")
+    '';
+})
diff --git a/nixos/tests/sogo.nix b/nixos/tests/sogo.nix
index acdad8d0f473b..e9059a2ab7734 100644
--- a/nixos/tests/sogo.nix
+++ b/nixos/tests/sogo.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "sogo";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ajs124 das_j ];
+    maintainers = [];
   };
 
   nodes = {
diff --git a/nixos/tests/sonic-server.nix b/nixos/tests/sonic-server.nix
new file mode 100644
index 0000000000000..bb98047619b2b
--- /dev/null
+++ b/nixos/tests/sonic-server.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "sonic-server";
+
+  meta = {
+    maintainers = with lib.maintainers; [ anthonyroussel ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    services.sonic-server.enable = true;
+  };
+
+  testScript = ''
+    machine.start()
+
+    machine.wait_for_unit("sonic-server.service")
+    machine.wait_for_open_port(1491)
+
+    with subtest("Check control mode"):
+      result = machine.succeed('(echo START control; sleep 1; echo PING; echo QUIT) | nc localhost 1491').splitlines()
+      assert result[2] == "PONG", f"expected 'PONG', got '{result[2]}'"
+  '';
+})
diff --git a/nixos/tests/sourcehut.nix b/nixos/tests/sourcehut.nix
index 87e6d82bdd8f4..0b258acc2af1d 100644
--- a/nixos/tests/sourcehut.nix
+++ b/nixos/tests/sourcehut.nix
@@ -126,6 +126,7 @@ in
     virtualisation.diskSize = 4 * 1024;
     virtualisation.memorySize = 2 * 1024;
     networking.domain = domain;
+    networking.enableIPv6 = false;
     networking.extraHosts = ''
       ${config.networking.primaryIPAddress} builds.${domain}
       ${config.networking.primaryIPAddress} git.${domain}
@@ -134,11 +135,6 @@ in
 
     services.sourcehut = {
       enable = true;
-      services = [
-        "builds"
-        "git"
-        "meta"
-      ];
       nginx.enable = true;
       nginx.virtualHost = {
         forceSSL = true;
diff --git a/nixos/tests/spark/default.nix b/nixos/tests/spark/default.nix
index 462f0d23a4032..034e9711bed52 100644
--- a/nixos/tests/spark/default.nix
+++ b/nixos/tests/spark/default.nix
@@ -1,28 +1,48 @@
-import ../make-test-python.nix ({...}: {
-  name = "spark";
+{ pkgs, ... }:
 
-  nodes = {
-    worker = { nodes, pkgs, ... }: {
-      services.spark.worker = {
-        enable = true;
-        master = "master:7077";
+let
+  inherit (pkgs) lib;
+  tests = {
+    default = testsForPackage { sparkPackage = pkgs.spark; };
+  };
+
+  testsForPackage = args: lib.recurseIntoAttrs {
+    sparkCluster = testSparkCluster args;
+    passthru.override = args': testsForPackage (args // args');
+  };
+  testSparkCluster = { sparkPackage, ... }: pkgs.testers.nixosTest ({
+    name = "spark";
+
+    nodes = {
+      worker = { nodes, pkgs, ... }: {
+        services.spark = {
+          package = sparkPackage;
+          worker = {
+            enable = true;
+            master = "master:7077";
+          };
+        };
+        virtualisation.memorySize = 2048;
       };
-      virtualisation.memorySize = 2048;
-    };
-    master = { config, pkgs, ... }: {
-      services.spark.master = {
-        enable = true;
-        bind = "0.0.0.0";
+      master = { config, pkgs, ... }: {
+        services.spark = {
+          package = sparkPackage;
+          master = {
+            enable = true;
+            bind = "0.0.0.0";
+          };
+        };
+        networking.firewall.allowedTCPPorts = [ 22 7077 8080 ];
       };
-      networking.firewall.allowedTCPPorts = [ 22 7077 8080 ];
     };
-  };
 
-  testScript = ''
-    master.wait_for_unit("spark-master.service")
-    worker.wait_for_unit("spark-worker.service")
-    worker.copy_from_host( "${./spark_sample.py}", "/spark_sample.py" )
-    assert "<title>Spark Master at spark://" in worker.succeed("curl -sSfkL http://master:8080/")
-    worker.succeed("spark-submit --master spark://master:7077 --executor-memory 512m --executor-cores 1 /spark_sample.py")
-  '';
-})
+    testScript = ''
+      master.wait_for_unit("spark-master.service")
+      worker.wait_for_unit("spark-worker.service")
+      worker.copy_from_host( "${./spark_sample.py}", "/spark_sample.py" )
+      assert "<title>Spark Master at spark://" in worker.succeed("curl -sSfkL http://master:8080/")
+      worker.succeed("spark-submit --version | systemd-cat")
+      worker.succeed("spark-submit --master spark://master:7077 --executor-memory 512m --executor-cores 1 /spark_sample.py")
+    '';
+  });
+in tests
diff --git a/nixos/tests/sqlite3-to-mysql.nix b/nixos/tests/sqlite3-to-mysql.nix
index 029058187df37..f18a442157e7b 100644
--- a/nixos/tests/sqlite3-to-mysql.nix
+++ b/nixos/tests/sqlite3-to-mysql.nix
@@ -19,7 +19,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
             python3Packages.pytest
             python3Packages.pytest-mock
             python3Packages.pytest-timeout
-            python3Packages.factory_boy
+            python3Packages.factory-boy
             python3Packages.docker # only needed so import does not fail
             sqlite3-to-mysql
           ])
diff --git a/nixos/tests/ssh-agent-auth.nix b/nixos/tests/ssh-agent-auth.nix
new file mode 100644
index 0000000000000..fee40afd61539
--- /dev/null
+++ b/nixos/tests/ssh-agent-auth.nix
@@ -0,0 +1,55 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+  let
+    inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+  in {
+    name = "ssh-agent-auth";
+    meta.maintainers = with lib.maintainers; [ nicoo ];
+
+    nodes = let nodeConfig = n: { ... }: {
+      users.users = {
+        admin = {
+          isNormalUser = true;
+          extraGroups = [ "wheel" ];
+          openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+        };
+        foo.isNormalUser = true;
+      };
+
+      security.pam.sshAgentAuth = {
+        # Must be specified, as nixpkgs CI expects everything to eval without warning
+        authorizedKeysFiles = [ "/etc/ssh/authorized_keys.d/%u" ];
+        enable = true;
+      };
+      security.${lib.replaceStrings [ "_" ] [ "-" ] n} = {
+        enable = true;
+        wheelNeedsPassword = true;  # We are checking `pam_ssh_agent_auth(8)` works for a sudoer
+      };
+
+      # Necessary for pam_ssh_agent_auth  >_>'
+      services.openssh.enable = true;
+    };
+    in lib.genAttrs [ "sudo" "sudo_rs" ] nodeConfig;
+
+    testScript = let
+      privateKeyPath = "/home/admin/.ssh/id_ecdsa";
+      userScript = pkgs.writeShellScript "test-script" ''
+        set -e
+        ssh-add -q ${privateKeyPath}
+
+        # faketty needed to ensure `sudo` doesn't write to the controlling PTY,
+        #  which would break the test-driver's line-oriented protocol.
+        ${lib.getExe pkgs.faketty} sudo -u foo -- id -un
+      '';
+    in ''
+      for vm in (sudo, sudo_rs):
+        sudo_impl = vm.name.replace("_", "-")
+        with subtest(f"wheel user can auth with ssh-agent for {sudo_impl}"):
+            vm.copy_from_host("${snakeOilPrivateKey}", "${privateKeyPath}")
+            vm.succeed("chmod -R 0700 /home/admin")
+            vm.succeed("chown -R admin:users /home/admin")
+
+            # Run `userScript` in an environment with an SSH-agent available
+            assert vm.succeed("sudo -u admin -- ssh-agent ${userScript} 2>&1").strip() == "foo"
+    '';
+  }
+)
diff --git a/nixos/tests/ssh-audit.nix b/nixos/tests/ssh-audit.nix
new file mode 100644
index 0000000000000..25772aba3ea08
--- /dev/null
+++ b/nixos/tests/ssh-audit.nix
@@ -0,0 +1,104 @@
+import ./make-test-python.nix (
+  {pkgs, ...}: let
+    sshKeys = import (pkgs.path + "/nixos/tests/ssh-keys.nix") pkgs;
+    sshUsername = "any-user";
+    serverName = "server";
+    clientName = "client";
+    sshAuditPort = 2222;
+  in {
+    name = "ssh";
+
+    nodes = {
+      "${serverName}" = {
+        networking.firewall.allowedTCPPorts = [
+          sshAuditPort
+        ];
+        services.openssh.enable = true;
+        users.users."${sshUsername}" = {
+          isNormalUser = true;
+          openssh.authorizedKeys.keys = [
+            sshKeys.snakeOilPublicKey
+          ];
+        };
+      };
+      "${clientName}" = {
+        programs.ssh = {
+          ciphers = [
+            "aes128-ctr"
+            "aes128-gcm@openssh.com"
+            "aes192-ctr"
+            "aes256-ctr"
+            "aes256-gcm@openssh.com"
+            "chacha20-poly1305@openssh.com"
+          ];
+          extraConfig = ''
+            IdentitiesOnly yes
+          '';
+          hostKeyAlgorithms = [
+            "rsa-sha2-256"
+            "rsa-sha2-256-cert-v01@openssh.com"
+            "rsa-sha2-512"
+            "rsa-sha2-512-cert-v01@openssh.com"
+            "sk-ssh-ed25519-cert-v01@openssh.com"
+            "sk-ssh-ed25519@openssh.com"
+            "ssh-ed25519"
+            "ssh-ed25519-cert-v01@openssh.com"
+          ];
+          kexAlgorithms = [
+            "curve25519-sha256"
+            "curve25519-sha256@libssh.org"
+            "diffie-hellman-group-exchange-sha256"
+            "diffie-hellman-group16-sha512"
+            "diffie-hellman-group18-sha512"
+            "sntrup761x25519-sha512@openssh.com"
+          ];
+          macs = [
+            "hmac-sha2-256-etm@openssh.com"
+            "hmac-sha2-512-etm@openssh.com"
+            "umac-128-etm@openssh.com"
+          ];
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      ${serverName}.wait_for_open_port(22)
+
+      # Should pass SSH server audit
+      ${serverName}.succeed("${pkgs.ssh-audit}/bin/ssh-audit 127.0.0.1")
+
+      # Wait for client to be able to connect to the server
+      ${clientName}.systemctl("start network-online.target")
+      ${clientName}.wait_for_unit("network-online.target")
+
+      # Set up trusted private key
+      ${clientName}.succeed("cat ${sshKeys.snakeOilPrivateKey} > privkey.snakeoil")
+      ${clientName}.succeed("chmod 600 privkey.snakeoil")
+
+      # Fail fast and disable interactivity
+      ssh_options = "-o BatchMode=yes -o ConnectTimeout=1 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
+
+      # Should deny root user
+      ${clientName}.fail(f"ssh {ssh_options} root@${serverName} true")
+
+      # Should deny non-root user password login
+      ${clientName}.fail(f"ssh {ssh_options} -o PasswordAuthentication=yes ${sshUsername}@${serverName} true")
+
+      # Should allow non-root user certificate login
+      ${clientName}.succeed(f"ssh {ssh_options} -i privkey.snakeoil ${sshUsername}@${serverName} true")
+
+      # Should pass SSH client audit
+      service_name = "ssh-audit.service"
+      ${serverName}.succeed(f"systemd-run --unit={service_name} ${pkgs.ssh-audit}/bin/ssh-audit --client-audit --port=${toString sshAuditPort}")
+      ${clientName}.sleep(5) # We can't use wait_for_open_port because ssh-audit exits as soon as anything talks to it
+      ${clientName}.execute(
+          f"ssh {ssh_options} -i privkey.snakeoil -p ${toString sshAuditPort} ${sshUsername}@${serverName} true",
+          check_return=False,
+          timeout=10
+      )
+      ${serverName}.succeed(f"exit $(systemctl show --property=ExecMainStatus --value {service_name})")
+    '';
+  }
+)
diff --git a/nixos/tests/sslh.nix b/nixos/tests/sslh.nix
index 17094606e8e6b..30ffd389d4422 100644
--- a/nixos/tests/sslh.nix
+++ b/nixos/tests/sslh.nix
@@ -10,21 +10,13 @@ import ./make-test-python.nix {
           prefixLength = 64;
         }
       ];
-      # sslh is really slow when reverse dns does not work
-      networking.hosts = {
-        "fe00:aa:bb:cc::2" = [ "server" ];
-        "fe00:aa:bb:cc::1" = [ "client" ];
-      };
       services.sslh = {
         enable = true;
-        transparent = true;
-        appendConfig = ''
-          protocols:
-          (
-            { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
-            { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
-          );
-        '';
+        settings.transparent = true;
+        settings.protocols = [
+          { name = "ssh"; service = "ssh"; host = "localhost"; port = "22"; probe = "builtin"; }
+          { name = "http"; host = "localhost"; port = "80"; probe = "builtin"; }
+        ];
       };
       services.openssh.enable = true;
       users.users.root.openssh.authorizedKeys.keyFiles = [ ./initrd-network-ssh/id_ed25519.pub ];
diff --git a/nixos/tests/stratis/encryption.nix b/nixos/tests/stratis/encryption.nix
index a555ff8a8e854..81b5f92b4ac4a 100644
--- a/nixos/tests/stratis/encryption.nix
+++ b/nixos/tests/stratis/encryption.nix
@@ -26,7 +26,7 @@ import ../make-test-python.nix ({ pkgs, ... }:
         # 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")
+        machine.succeed("stratis pool stop  --name testpool")
+        machine.succeed("stratis pool start --name testpool --unlock-method keyring")
       '';
   })
diff --git a/nixos/tests/stub-ld.nix b/nixos/tests/stub-ld.nix
new file mode 100644
index 0000000000000..25161301741b7
--- /dev/null
+++ b/nixos/tests/stub-ld.nix
@@ -0,0 +1,73 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "stub-ld";
+
+  nodes.machine = { lib, ... }:
+    {
+      environment.stub-ld.enable = true;
+
+      specialisation.nostub = {
+        inheritParentConfig = true;
+
+        configuration = { ... }: {
+          environment.stub-ld.enable = lib.mkForce false;
+        };
+      };
+    };
+
+  testScript = let
+    libDir = pkgs.stdenv.hostPlatform.libDir;
+    ldsoBasename = lib.last (lib.splitString "/" pkgs.stdenv.cc.bintools.dynamicLinker);
+
+    check32 = pkgs.stdenv.isx86_64;
+    pkgs32 = pkgs.pkgsi686Linux;
+
+    libDir32 = pkgs32.stdenv.hostPlatform.libDir;
+    ldsoBasename32 = lib.last (lib.splitString "/" pkgs32.stdenv.cc.bintools.dynamicLinker);
+
+    test-exec = builtins.mapAttrs (n: v: pkgs.runCommand "test-exec-${n}" { src = pkgs.fetchurl v; } "mkdir -p $out;cd $out;tar -xzf $src") {
+      x86_64-linux.url = "https://github.com/rustic-rs/rustic/releases/download/v0.6.1/rustic-v0.6.1-x86_64-unknown-linux-gnu.tar.gz";
+      x86_64-linux.hash = "sha256-3zySzx8MKFprMOi++yr2ZGASE0aRfXHQuG3SN+kWUCI=";
+      i686-linux.url = "https://github.com/rustic-rs/rustic/releases/download/v0.6.1/rustic-v0.6.1-i686-unknown-linux-gnu.tar.gz";
+      i686-linux.hash = "sha256-fWNiATFeg0B2pfB5zndlnzGn7Ztl8diVS1rFLEDnSLU=";
+      aarch64-linux.url = "https://github.com/rustic-rs/rustic/releases/download/v0.6.1/rustic-v0.6.1-aarch64-unknown-linux-gnu.tar.gz";
+      aarch64-linux.hash = "sha256-hnldbd2cctQIAhIKoEZLIWY8H3jiFBClkNy2UlyyvAs=";
+    };
+    exec-name = "rustic";
+
+    if32 = pythonStatement: if check32 then pythonStatement else "pass";
+  in
+    ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      with subtest("Check for stub (enabled, initial)"):
+          machine.succeed('test -L /${libDir}/${ldsoBasename}')
+          ${if32 "machine.succeed('test -L /${libDir32}/${ldsoBasename32}')"}
+
+      with subtest("Try FHS executable"):
+          machine.copy_from_host('${test-exec.${pkgs.system}}','test-exec')
+          machine.succeed('if test-exec/${exec-name} 2>outfile; then false; else [ $? -eq 127 ];fi')
+          machine.succeed('grep -qi nixos outfile')
+          ${if32 "machine.copy_from_host('${test-exec.${pkgs32.system}}','test-exec32')"}
+          ${if32 "machine.succeed('if test-exec32/${exec-name} 2>outfile32; then false; else [ $? -eq 127 ];fi')"}
+          ${if32 "machine.succeed('grep -qi nixos outfile32')"}
+
+      with subtest("Disable stub"):
+          machine.succeed("/run/booted-system/specialisation/nostub/bin/switch-to-configuration test")
+
+      with subtest("Check for stub (disabled)"):
+          machine.fail('test -e /${libDir}/${ldsoBasename}')
+          ${if32 "machine.fail('test -e /${libDir32}/${ldsoBasename32}')"}
+
+      with subtest("Create file in stub location (to be overwritten)"):
+          machine.succeed('mkdir -p /${libDir};touch /${libDir}/${ldsoBasename}')
+          ${if32 "machine.succeed('mkdir -p /${libDir32};touch /${libDir32}/${ldsoBasename32}')"}
+
+      with subtest("Re-enable stub"):
+          machine.succeed("/run/booted-system/bin/switch-to-configuration test")
+
+      with subtest("Check for stub (enabled, final)"):
+          machine.succeed('test -L /${libDir}/${ldsoBasename}')
+          ${if32 "machine.succeed('test -L /${libDir32}/${ldsoBasename32}')"}
+    '';
+})
diff --git a/nixos/tests/stunnel.nix b/nixos/tests/stunnel.nix
index 22c087290fc7b..f8cfa0414761d 100644
--- a/nixos/tests/stunnel.nix
+++ b/nixos/tests/stunnel.nix
@@ -17,11 +17,18 @@ let
     };
   };
   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
+    systemd.services.create-test-cert = {
+      wantedBy = [ "sysinit.target" ];
+      before = [ "sysinit.target" "shutdown.target" ];
+      conflicts = [ "shutdown.target" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.Type = "oneshot";
+      script = ''
+        ${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 ];
diff --git a/nixos/tests/sudo-rs.nix b/nixos/tests/sudo-rs.nix
new file mode 100644
index 0000000000000..753e00686e956
--- /dev/null
+++ b/nixos/tests/sudo-rs.nix
@@ -0,0 +1,95 @@
+# Some tests to ensure sudo is working properly.
+{ pkgs, ... }:
+let
+  inherit (pkgs.lib) mkIf optionalString;
+  password = "helloworld";
+in
+  import ./make-test-python.nix ({ lib, pkgs, ...} : {
+    name = "sudo-rs";
+    meta.maintainers = pkgs.sudo-rs.meta.maintainers;
+
+    nodes.machine =
+      { lib, ... }:
+      {
+        environment.systemPackages = [ pkgs.faketty ];
+        users.groups = { foobar = {}; barfoo = {}; baz = { gid = 1337; }; };
+        users.users = {
+          test0 = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+          test1 = { isNormalUser = true; password = password; };
+          test2 = { isNormalUser = true; extraGroups = [ "foobar" ]; password = password; };
+          test3 = { isNormalUser = true; extraGroups = [ "barfoo" ]; };
+          test4 = { isNormalUser = true; extraGroups = [ "baz" ]; };
+          test5 = { isNormalUser = true; };
+        };
+
+        security.sudo-rs = {
+          enable = true;
+          wheelNeedsPassword = false;
+
+          extraRules = [
+            # SUDOERS SYNTAX CHECK (Test whether the module produces a valid output;
+            # errors being detected by the visudo checks.
+
+            # These should not create any entries
+            { users = [ "notest1" ]; commands = [ ]; }
+            { commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+            # Test defining commands with the options syntax, though not setting any options
+            { users = [ "notest2" ]; commands = [ { command = "ALL"; options = [ ]; } ]; }
+
+
+            # CONFIGURATION FOR TEST CASES
+            { users = [ "test1" ]; groups = [ "foobar" ]; commands = [ "ALL" ]; }
+            { groups = [ "barfoo" 1337 ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; }
+            { users = [ "test5" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; runAs = "test1:barfoo"; }
+          ];
+        };
+      };
+
+    nodes.strict = { ... }: {
+      environment.systemPackages = [ pkgs.faketty ];
+      users.users = {
+        admin = { isNormalUser = true; extraGroups = [ "wheel" ]; };
+        noadmin = { isNormalUser = true; };
+      };
+
+      security.sudo-rs = {
+        enable = true;
+        wheelNeedsPassword = false;
+        execWheelOnly = true;
+      };
+    };
+
+    testScript =
+      ''
+        with subtest("users in wheel group should have passwordless sudo"):
+            machine.succeed('faketty -- su - test0 -c "sudo -u root true"')
+
+        with subtest("test1 user should have sudo with password"):
+            machine.succeed('faketty -- su - test1 -c "echo ${password} | sudo -S -u root true"')
+
+        with subtest("test1 user should not be able to use sudo without password"):
+            machine.fail('faketty -- su - test1 -c "sudo -n -u root true"')
+
+        with subtest("users in group 'foobar' should be able to use sudo with password"):
+            machine.succeed('faketty -- su - test2 -c "echo ${password} | sudo -S -u root true"')
+
+        with subtest("users in group 'barfoo' should be able to use sudo without password"):
+            machine.succeed("sudo -u test3 sudo -n -u root true")
+
+        with subtest("users in group 'baz' (GID 1337)"):
+            machine.succeed("sudo -u test4 sudo -n -u root echo true")
+
+        with subtest("test5 user should be able to run commands under test1"):
+            machine.succeed("sudo -u test5 sudo -n -u test1 true")
+
+        with subtest("test5 user should not be able to run commands under root"):
+            machine.fail("sudo -u test5 sudo -n -u root true 2>/dev/null")
+
+        with subtest("users in wheel should be able to run sudo despite execWheelOnly"):
+            strict.succeed('faketty -- su - admin -c "sudo -u root true"')
+
+        with subtest("non-wheel users should be unable to run sudo thanks to execWheelOnly"):
+            strict.fail('faketty -- su - noadmin -c "sudo --help"')
+      '';
+  })
diff --git a/nixos/tests/sudo.nix b/nixos/tests/sudo.nix
index 3b6028c6f0b09..1fe478f0bff14 100644
--- a/nixos/tests/sudo.nix
+++ b/nixos/tests/sudo.nix
@@ -5,7 +5,7 @@ let
 in
   import ./make-test-python.nix ({ lib, pkgs, ...} : {
     name = "sudo";
-    meta.maintainers = with lib.maintainers; [ lschuermann ];
+    meta.maintainers = pkgs.sudo.meta.maintainers;
 
     nodes.machine =
       { lib, ... }:
@@ -21,7 +21,8 @@ in
         };
 
         security.sudo = {
-          enable = true;
+          # Explicitly _not_ defining 'enable = true;' here, to check that sudo is enabled by default
+
           wheelNeedsPassword = false;
 
           extraConfig = ''
diff --git a/nixos/tests/suwayomi-server.nix b/nixos/tests/suwayomi-server.nix
new file mode 100644
index 0000000000000..36072028380b8
--- /dev/null
+++ b/nixos/tests/suwayomi-server.nix
@@ -0,0 +1,46 @@
+{ system ? builtins.currentSystem
+, pkgs
+, lib ? pkgs.lib
+}:
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (lib) recursiveUpdate;
+
+  baseTestConfig = {
+    meta.maintainers = with lib.maintainers; [ ratcornu ];
+    nodes.machine = { pkgs, ... }: {
+      services.suwayomi-server = {
+        enable = true;
+        settings.server.port = 1234;
+      };
+    };
+    testScript = ''
+      machine.wait_for_unit("suwayomi-server.service")
+      machine.wait_for_open_port(1234)
+      machine.succeed("curl --fail http://localhost:1234/")
+    '';
+  };
+in
+
+{
+  without-auth = makeTest (recursiveUpdate baseTestConfig {
+    name = "suwayomi-server-without-auth";
+  });
+
+  with-auth = makeTest (recursiveUpdate baseTestConfig {
+    name = "suwayomi-server-with-auth";
+
+    nodes.machine = { pkgs, ... }: {
+      services.suwayomi-server = {
+        enable = true;
+
+        settings.server = {
+          port = 1234;
+          basicAuthEnabled = true;
+          basicAuthUsername = "alice";
+          basicAuthPasswordFile = pkgs.writeText "snakeoil-pass.txt" "pass";
+        };
+      };
+    };
+  });
+}
diff --git a/nixos/tests/sway.nix b/nixos/tests/sway.nix
index 695d4a7708104..185c5b1b0aa90 100644
--- a/nixos/tests/sway.nix
+++ b/nixos/tests/sway.nix
@@ -134,7 +134,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     machine.wait_for_file("/tmp/sway-ipc.sock")
 
     # Test XWayland (foot does not support X):
-    swaymsg("exec WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY=invalid alacritty")
+    swaymsg("exec WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= alacritty")
     wait_for_window("alice@machine")
     machine.send_chars("test-x11\n")
     machine.wait_for_file("/tmp/test-x11-exit-ok")
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index 529a20864206d..5ffdf180d5e3f 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -58,6 +58,37 @@ in {
       '');
 
       specialisation = rec {
+        brokenInitInterface.configuration.config.system.extraSystemBuilderCmds = ''
+          echo "systemd 0" > $out/init-interface-version
+        '';
+
+        modifiedSystemConf.configuration.systemd.extraConfig = ''
+          # Hello world!
+        '';
+
+        addedMount.configuration.virtualisation.fileSystems."/test" = {
+          device = "tmpfs";
+          fsType = "tmpfs";
+        };
+
+        addedMountOptsModified.configuration = {
+          imports = [ addedMount.configuration ];
+          virtualisation.fileSystems."/test".options = [ "x-test" ];
+        };
+
+        addedMountDevModified.configuration = {
+          imports = [ addedMountOptsModified.configuration ];
+          virtualisation.fileSystems."/test".device = lib.mkForce "ramfs";
+        };
+
+        storeMountModified.configuration = {
+          virtualisation.fileSystems."/".device = lib.mkForce "auto";
+        };
+
+        swap.configuration.swapDevices = lib.mkVMOverride [
+          { device = "/swapfile"; size = 1; }
+        ];
+
         simpleService.configuration = {
           systemd.services.test = {
             wantedBy = [ "multi-user.target" ];
@@ -643,6 +674,97 @@ in {
 
         # test and dry-activate actions are tested further down below
 
+        # invalid action fails the script
+        switch_to_specialisation("${machine}", "", action="broken-action", fail=True)
+        # no action fails the script
+        assert "Usage:" in machine.fail("${machine}/bin/switch-to-configuration 2>&1")
+
+    with subtest("init interface version"):
+        # Do not try to switch to an invalid init interface version
+        assert "incompatible" in switch_to_specialisation("${machine}", "brokenInitInterface", fail=True)
+
+    with subtest("systemd restarts"):
+        # systemd is restarted when its system.conf changes
+        out = switch_to_specialisation("${machine}", "modifiedSystemConf")
+        assert_contains(out, "restarting systemd...")
+
+    with subtest("continuing from an aborted switch"):
+        # An aborted switch will write into a file what it tried to start
+        # and a second switch should continue from this
+        machine.succeed("echo dbus.service > /run/nixos/start-list")
+        out = switch_to_specialisation("${machine}", "modifiedSystemConf")
+        assert_contains(out, "starting the following units: dbus.service\n")
+
+    with subtest("fstab mounts"):
+        switch_to_specialisation("${machine}", "")
+        # add a mountpoint
+        out = switch_to_specialisation("${machine}", "addedMount")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: test.mount\n")
+        # modify the mountpoint's options
+        out = switch_to_specialisation("${machine}", "addedMountOptsModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: test.mount\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # modify the device
+        out = switch_to_specialisation("${machine}", "addedMountDevModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.mount\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # modify both
+        out = switch_to_specialisation("${machine}", "addedMount")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_lacks(out, "reloading the following units:")
+        assert_contains(out, "\nrestarting the following units: test.mount\n")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # remove the mount
+        out = switch_to_specialisation("${machine}", "")
+        assert_contains(out, "stopping the following units: test.mount\n")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+        # change something about the / mount
+        out = switch_to_specialisation("${machine}", "storeMountModified")
+        assert_lacks(out, "stopping the following units:")
+        assert_contains(out, "NOT restarting the following changed units: -.mount")
+        assert_contains(out, "reloading the following units: dbus.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
+    with subtest("swaps"):
+        switch_to_specialisation("${machine}", "")
+        # add a swap
+        out = switch_to_specialisation("${machine}", "swap")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_contains(out, "the following new units were started: swapfile.swap")
+        # remove it
+        out = switch_to_specialisation("${machine}", "")
+        assert_contains(out, "stopping swap device: /swapfile")
+        assert_lacks(out, "stopping the following units:")
+        assert_lacks(out, "NOT restarting the following changed units:")
+        assert_contains(out, "reloading the following units: dbus.service\n")
+        assert_lacks(out, "\nrestarting the following units:")
+        assert_lacks(out, "\nstarting the following units:")
+        assert_lacks(out, "the following new units were started:")
+
     with subtest("services"):
         switch_to_specialisation("${machine}", "")
         # Nothing happens when nothing is changed
diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing-init.nix
index 195c157ffb6e8..97fcf2ad28d10 100644
--- a/nixos/tests/syncthing-init.nix
+++ b/nixos/tests/syncthing-init.nix
@@ -1,7 +1,6 @@
 import ./make-test-python.nix ({ lib, pkgs, ... }: let
 
   testId = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
-  testName = "testDevice foo'bar";
 
 in {
   name = "syncthing-init";
diff --git a/nixos/tests/syncthing-many-devices.nix b/nixos/tests/syncthing-many-devices.nix
new file mode 100644
index 0000000000000..2251bf0774533
--- /dev/null
+++ b/nixos/tests/syncthing-many-devices.nix
@@ -0,0 +1,203 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+
+# This nixosTest is supposed to check the following:
+#
+# - Whether syncthing's API handles multiple requests for many devices, see
+#   https://github.com/NixOS/nixpkgs/issues/260262
+#
+# - Whether syncthing-init.service generated bash script removes devices and
+#   folders that are not present in the user's configuration, which is partly
+#   injected into the script. See also:
+#   https://github.com/NixOS/nixpkgs/issues/259256
+#
+
+let
+  # Just a long path not to copy paste
+  configPath = "/var/lib/syncthing/.config/syncthing/config.xml";
+
+  # We will iterate this and more attribute sets defined here, later in the
+  # testScript. Start with this, and distinguish these settings from other
+  # settings, as we check these differently with xmllint, due to the ID.
+  settingsWithId = {
+    devices = {
+      # All of the device IDs used here were generated by the following command:
+      #
+      #    (${pkgs.syncthing}/bin/syncthing generate --home /tmp/foo\
+      #       | grep ID: | sed 's/.*ID: *//') && rm -rf /tmp/foo
+      #
+      # See also discussion at:
+      # https://forum.syncthing.net/t/how-to-generate-dummy-device-ids/20927/8
+      test_device1.id  = "IVTZ5XF-EF3GKFT-GS4AZLG-IT6H2ZP-6WK75SF-AFXQXJJ-BNRZ4N6-XPDKVAU";
+      test_device2.id  = "5C35H56-Z2GFF4F-F3IVD4B-GJYVWIE-SMDBJZN-GI66KWP-52JIQGN-4AVLYAM";
+      test_device3.id  = "XKLSKHE-BZOHV7B-WQZACEF-GTH36NP-6JSBB6L-RXS3M7C-EEVWO2L-C5B4OAJ";
+      test_device4.id  = "APN5Q7J-35GZETO-5KCLF35-ZA7KBWK-HGWPBNG-FERF24R-UTLGMEX-4VJ6PQX";
+      test_device5.id  = "D4YXQEE-5MK6LIK-BRU5QWM-ZRXJCK2-N3RQBJE-23JKTQQ-LYGDPHF-RFPZIQX";
+      test_device6.id  = "TKMCH64-T44VSLI-6FN2YLF-URBZOBR-ATO4DYX-GEDRIII-CSMRQAI-UAQMDQG";
+      test_device7.id  = "472EEBG-Q4PZCD4-4CX6PGF-XS3FSQ2-UFXBZVB-PGNXWLX-7FKBLER-NJ3EMAR";
+      test_device8.id  = "HW6KUMK-WTBG24L-2HZQXLO-TGJSG2M-2JG3FHX-5OGYRUJ-T6L5NN7-L364QAZ";
+      test_device9.id  = "YAE24AP-7LSVY4T-J74ZSEM-A2IK6RB-FGA35TP-AG4CSLU-ED4UYYY-2J2TDQU";
+      test_device10.id = "277XFSB-OFMQOBI-3XGNGUE-Y7FWRV3-QQDADIY-QIIPQ26-EOGTYKW-JP2EXAI";
+      test_device11.id = "2WWXVTN-Q3QWAAY-XFORMRM-2FDI5XZ-OGN33BD-XOLL42R-DHLT2ML-QYXDQAU";
+    };
+    # Generates a few folders with IDs and paths as written...
+    folders = lib.pipe 6 [
+      (builtins.genList (x: {
+        name = "/var/lib/syncthing/test_folder${builtins.toString x}";
+        value = {
+          id = "DontDeleteMe${builtins.toString x}";
+        };
+      }))
+      builtins.listToAttrs
+    ];
+  };
+  # Non default options that we check later if were applied
+  settingsWithoutId = {
+    options = {
+      autoUpgradeIntervalH = 0;
+      urAccepted = -1;
+    };
+    gui = {
+      theme = "dark";
+    };
+  };
+  # Used later when checking whether settings were set in config.xml:
+  checkSettingWithId = { t # t for type
+  , id
+  , not ? false
+  }: ''
+    print("Searching for a ${t} with id ${id}")
+    configVal_${t} = machine.succeed(
+        "${pkgs.libxml2}/bin/xmllint "
+        "--xpath 'string(//${t}[@id=\"${id}\"]/@id)' ${configPath}"
+    )
+    print("${t}.id = {}".format(configVal_${t}))
+    assert "${id}" ${if not then "not" else ""} in configVal_${t}
+  '';
+  # Same as checkSettingWithId, but for 'options' and 'gui'
+  checkSettingWithoutId = { t # t for type
+  , n # n for name
+  , v # v for value
+  , not ? false
+  }: ''
+    print("checking whether setting ${t}.${n} is set to ${v}")
+    configVal_${t}_${n} = machine.succeed(
+        "${pkgs.libxml2}/bin/xmllint "
+        "--xpath 'string(/configuration/${t}/${n})' ${configPath}"
+    )
+    print("${t}.${n} = {}".format(configVal_${t}_${n}))
+    assert "${v}" ${if not then "not" else ""} in configVal_${t}_${n}
+  '';
+  # Removes duplication a bit to define this function for the IDs to delete -
+  # we check whether they were added after our script ran, and before the
+  # systemd unit's bash script ran, and afterwards - whether the systemd unit
+  # worked.
+  checkSettingsToDelete = {
+    not
+  }: lib.pipe IDsToDelete [
+    (lib.mapAttrsToList (t: id:
+      checkSettingWithId {
+        inherit t id;
+        inherit not;
+      }
+    ))
+    lib.concatStrings
+  ];
+  # These IDs are added to syncthing using the API, similarly to how the
+  # generated systemd unit's bash script does it. Only we add it and expect the
+  # systemd unit bash script to remove them when executed.
+  IDsToDelete = {
+    # Also created using the syncthing generate command above
+    device = "LZ2CTHT-3W2M7BC-CMKDFZL-DLUQJFS-WJR73PA-NZGODWG-DZBHCHI-OXTQXAK";
+    # Intentionally this is a substring of the IDs of the 'test_folder's, as
+    # explained in: https://github.com/NixOS/nixpkgs/issues/259256
+    folder = "DeleteMe";
+  };
+  addDeviceToDeleteScript = pkgs.writers.writeBash "syncthing-add-device-to-delete.sh" ''
+    set -euo pipefail
+
+    export RUNTIME_DIRECTORY=/tmp
+
+    curl() {
+        # get the api key by parsing the config.xml
+        while
+            ! ${pkgs.libxml2}/bin/xmllint \
+                --xpath 'string(configuration/gui/apikey)' \
+                ${configPath} \
+                >"$RUNTIME_DIRECTORY/api_key"
+        do sleep 1; done
+
+        (printf "X-API-Key: "; cat "$RUNTIME_DIRECTORY/api_key") >"$RUNTIME_DIRECTORY/headers"
+
+        ${pkgs.curl}/bin/curl -sSLk -H "@$RUNTIME_DIRECTORY/headers" \
+            --retry 1000 --retry-delay 1 --retry-all-errors \
+            "$@"
+    }
+    curl -d ${lib.escapeShellArg (builtins.toJSON { deviceID = IDsToDelete.device;})} \
+        -X POST 127.0.0.1:8384/rest/config/devices
+    curl -d ${lib.escapeShellArg (builtins.toJSON { id = IDsToDelete.folder;})} \
+        -X POST 127.0.0.1:8384/rest/config/folders
+  '';
+in {
+  name = "syncthing-init";
+  meta.maintainers = with lib.maintainers; [ doronbehar ];
+
+  nodes.machine = {
+    services.syncthing = {
+      enable = true;
+      overrideDevices = true;
+      overrideFolders = true;
+      settings = settingsWithoutId // settingsWithId;
+    };
+  };
+  testScript = ''
+    machine.wait_for_unit("syncthing-init.service")
+  '' + (lib.pipe settingsWithId [
+    # Check that folders and devices were added properly and that all IDs exist
+    (lib.mapAttrsRecursive (path: id:
+      checkSettingWithId {
+        # plural -> solitary
+        t = (lib.removeSuffix "s" (builtins.elemAt path 0));
+        inherit id;
+      }
+    ))
+    # Get all the values we applied the above function upon
+    (lib.collect builtins.isString)
+    lib.concatStrings
+  ]) + (lib.pipe settingsWithoutId [
+    # Check that all other syncthing.settings were added properly with correct
+    # values
+    (lib.mapAttrsRecursive (path: value:
+      checkSettingWithoutId {
+        t = (builtins.elemAt path 0);
+        n = (builtins.elemAt path 1);
+        v = (builtins.toString value);
+      }
+    ))
+    # Get all the values we applied the above function upon
+    (lib.collect builtins.isString)
+    lib.concatStrings
+  ]) + ''
+    # Run the script on the machine
+    machine.succeed("${addDeviceToDeleteScript}")
+  '' + (checkSettingsToDelete {
+    not = false;
+  }) + ''
+    # Useful for debugging later
+    machine.copy_from_vm("${configPath}", "before")
+
+    machine.systemctl("restart syncthing-init.service")
+    machine.wait_for_unit("syncthing-init.service")
+  '' + (checkSettingsToDelete {
+    not = true;
+  }) + ''
+    # Useful for debugging later
+    machine.copy_from_vm("${configPath}", "after")
+
+    # Copy the systemd unit's bash script, to inspect it for debugging.
+    mergeScript = machine.succeed(
+        "systemctl cat syncthing-init.service | "
+        "${pkgs.initool}/bin/initool g - Service ExecStart --value-only"
+    ).strip() # strip from new lines
+    machine.copy_from_vm(mergeScript, "")
+  '';
+})
diff --git a/nixos/tests/sysinit-reactivation.nix b/nixos/tests/sysinit-reactivation.nix
new file mode 100644
index 0000000000000..1a0caecb610a3
--- /dev/null
+++ b/nixos/tests/sysinit-reactivation.nix
@@ -0,0 +1,107 @@
+# This runs to two scenarios but in one tests:
+# - A post-sysinit service needs to be restarted AFTER tmpfiles was restarted.
+# - A service needs to be restarted BEFORE tmpfiles is restarted
+
+{ lib, ... }:
+
+let
+  makeGeneration = generation: {
+    "${generation}".configuration = {
+      systemd.services.pre-sysinit-before-tmpfiles.environment.USER =
+        lib.mkForce "${generation}-tmpfiles-user";
+
+      systemd.services.pre-sysinit-after-tmpfiles.environment = {
+        NEEDED_PATH = lib.mkForce "/run/${generation}-needed-by-pre-sysinit-after-tmpfiles";
+        PATH_TO_CREATE = lib.mkForce "/run/${generation}-needed-by-post-sysinit";
+      };
+
+      systemd.services.post-sysinit.environment = {
+        NEEDED_PATH = lib.mkForce "/run/${generation}-needed-by-post-sysinit";
+        PATH_TO_CREATE = lib.mkForce "/run/${generation}-created-by-post-sysinit";
+      };
+
+      systemd.tmpfiles.settings.test = lib.mkForce {
+        "/run/${generation}-needed-by-pre-sysinit-after-tmpfiles".f.user =
+          "${generation}-tmpfiles-user";
+      };
+    };
+  };
+in
+
+{
+
+  name = "sysinit-reactivation";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { config, lib, pkgs, ... }: {
+    systemd.services.pre-sysinit-before-tmpfiles = {
+      wantedBy = [ "sysinit.target" ];
+      requiredBy = [ "sysinit-reactivation.target" ];
+      before = [ "systemd-tmpfiles-setup.service" "systemd-tmpfiles-resetup.service" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      environment.USER = "tmpfiles-user";
+      script = "${pkgs.shadow}/bin/useradd $USER";
+    };
+
+    systemd.services.pre-sysinit-after-tmpfiles = {
+      wantedBy = [ "sysinit.target" ];
+      requiredBy = [ "sysinit-reactivation.target" ];
+      after = [ "systemd-tmpfiles-setup.service" "systemd-tmpfiles-resetup.service" ];
+      unitConfig.DefaultDependencies = false;
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      environment = {
+        NEEDED_PATH = "/run/needed-by-pre-sysinit-after-tmpfiles";
+        PATH_TO_CREATE = "/run/needed-by-post-sysinit";
+      };
+      script = ''
+        if [[ -e $NEEDED_PATH ]]; then
+          touch $PATH_TO_CREATE
+        fi
+      '';
+    };
+
+    systemd.services.post-sysinit = {
+      wantedBy = [ "default.target" ];
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+      environment = {
+        NEEDED_PATH = "/run/needed-by-post-sysinit";
+        PATH_TO_CREATE = "/run/created-by-post-sysinit";
+      };
+      script = ''
+        if [[ -e $NEEDED_PATH ]]; then
+          touch $PATH_TO_CREATE
+        fi
+      '';
+    };
+
+    systemd.tmpfiles.settings.test = {
+      "/run/needed-by-pre-sysinit-after-tmpfiles".f.user =
+        "tmpfiles-user";
+    };
+
+    specialisation = lib.mkMerge [
+      (makeGeneration "second")
+      (makeGeneration "third")
+    ];
+  };
+
+  testScript = { nodes, ... }: ''
+    def switch(generation):
+      toplevel = "${nodes.machine.system.build.toplevel}";
+      machine.succeed(f"{toplevel}/specialisation/{generation}/bin/switch-to-configuration switch")
+
+    machine.wait_for_unit("default.target")
+    machine.succeed("test -e /run/created-by-post-sysinit")
+
+    switch("second")
+    machine.succeed("test -e /run/second-created-by-post-sysinit")
+
+    switch("third")
+    machine.succeed("test -e /run/third-created-by-post-sysinit")
+  '';
+}
diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index c1f8637989e39..c0b37a230df0f 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -18,7 +18,7 @@ in
 {
   basic = makeTest {
     name = "systemd-boot";
-    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer ];
+    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
 
     nodes.machine = common;
 
@@ -42,7 +42,7 @@ in
   # Check that specialisations create corresponding boot entries.
   specialisation = makeTest {
     name = "systemd-boot-specialisation";
-    meta.maintainers = with pkgs.lib.maintainers; [ lukegb ];
+    meta.maintainers = with pkgs.lib.maintainers; [ lukegb julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -65,7 +65,7 @@ in
   # Boot without having created an EFI entry--instead using default "/EFI/BOOT/BOOTX64.EFI"
   fallback = makeTest {
     name = "systemd-boot-fallback";
-    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer ];
+    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -91,7 +91,7 @@ in
 
   update = makeTest {
     name = "systemd-boot-update";
-    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer ];
+    meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
 
     nodes.machine = common;
 
@@ -107,13 +107,13 @@ in
       )
 
       output = machine.succeed("/run/current-system/bin/switch-to-configuration boot")
-      assert "updating systemd-boot from 000.0-1-notnixos to " in output
+      assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message"
     '';
   };
 
   memtest86 = makeTest {
     name = "systemd-boot-memtest86";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -128,7 +128,7 @@ in
 
   netbootxyz = makeTest {
     name = "systemd-boot-netbootxyz";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -143,7 +143,7 @@ in
 
   entryFilename = makeTest {
     name = "systemd-boot-entry-filename";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -160,7 +160,7 @@ in
 
   extraEntries = makeTest {
     name = "systemd-boot-extra-entries";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -179,7 +179,7 @@ in
 
   extraFiles = makeTest {
     name = "systemd-boot-extra-files";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -196,7 +196,7 @@ in
 
   switch-test = makeTest {
     name = "systemd-boot-switch-test";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime ];
+    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
 
     nodes = {
       inherit common;
@@ -252,11 +252,40 @@ in
     '';
   };
 
+  garbage-collect-entry = makeTest {
+    name = "systemd-boot-garbage-collect-entry";
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+
+    nodes = {
+      inherit common;
+      machine = { pkgs, nodes, ... }: {
+        imports = [ common ];
+
+        # These are configs for different nodes, but we'll use them here in `machine`
+        system.extraDependencies = [
+          nodes.common.system.build.toplevel
+        ];
+      };
+    };
+
+    testScript = { nodes, ... }:
+      let
+        baseSystem = nodes.common.system.build.toplevel;
+      in
+      ''
+        machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}")
+        machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1")
+        machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
+        machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf")
+        machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf")
+      '';
+  };
+
   # Some UEFI firmwares fail on large reads. Now that systemd-boot loads initrd
   # itself, systems with such firmware won't boot without this fix
   uefiLargeFileWorkaround = makeTest {
     name = "uefi-large-file-workaround";
-
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
     nodes.machine = { pkgs, ... }: {
       imports = [common];
       virtualisation.efi.OVMF = pkgs.OVMF.overrideAttrs (old: {
@@ -277,4 +306,20 @@ in
       machine.wait_for_unit("multi-user.target")
     '';
   };
+
+  no-bootspec = makeTest
+    {
+      name = "systemd-boot-no-bootspec";
+      meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+
+      nodes.machine = {
+        imports = [ common ];
+        boot.bootspec.enable = false;
+      };
+
+      testScript = ''
+        machine.start()
+        machine.wait_for_unit("multi-user.target")
+      '';
+    };
 }
diff --git a/nixos/tests/systemd-credentials-tpm2.nix b/nixos/tests/systemd-credentials-tpm2.nix
index d2dc1fd7b615b..bf74183122368 100644
--- a/nixos/tests/systemd-credentials-tpm2.nix
+++ b/nixos/tests/systemd-credentials-tpm2.nix
@@ -1,13 +1,4 @@
-import ./make-test-python.nix ({ lib, pkgs, system, ... }:
-
-let
-  tpmSocketPath = "/tmp/swtpm-sock";
-  tpmDeviceModels = {
-    x86_64-linux = "tpm-tis";
-    aarch64-linux = "tpm-tis-device";
-  };
-in
-
+import ./make-test-python.nix ({ lib, pkgs, ... }:
 {
   name = "systemd-credentials-tpm2";
 
@@ -16,51 +7,11 @@ in
   };
 
   nodes.machine = { pkgs, ... }: {
-    virtualisation = {
-      qemu.options = [
-        "-chardev socket,id=chrtpm,path=${tpmSocketPath}"
-        "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm"
-        "-device ${tpmDeviceModels.${system}},tpmdev=tpm_dev_0"
-      ];
-    };
-
-    boot.initrd.availableKernelModules = [ "tpm_tis" ];
-
+    virtualisation.tpm.enable = true;
     environment.systemPackages = with pkgs; [ diffutils ];
   };
 
   testScript = ''
-    import subprocess
-    from tempfile import TemporaryDirectory
-
-    # From systemd-initrd-luks-tpm2.nix
-    class Tpm:
-        def __init__(self):
-            self.state_dir = TemporaryDirectory()
-            self.start()
-
-        def start(self):
-            self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm",
-                "socket",
-                "--tpmstate", f"dir={self.state_dir.name}",
-                "--ctrl", "type=unixio,path=${tpmSocketPath}",
-                "--tpm2",
-                ])
-
-            # Check whether starting swtpm failed
-            try:
-                exit_code = self.proc.wait(timeout=0.2)
-                if exit_code is not None and exit_code != 0:
-                    raise Exception("failed to start swtpm")
-            except subprocess.TimeoutExpired:
-                pass
-
-        """Check whether the swtpm process exited due to an error"""
-        def check(self):
-            exit_code = self.proc.poll()
-            if exit_code is not None and exit_code != 0:
-                raise Exception("swtpm process died")
-
     CRED_NAME = "testkey"
     CRED_RAW_FILE = f"/root/{CRED_NAME}"
     CRED_FILE = f"/root/{CRED_NAME}.cred"
@@ -85,12 +36,6 @@ in
 
         machine.log("systemd-run finished successfully")
 
-    tpm = Tpm()
-
-    @polling_condition
-    def swtpm_running():
-        tpm.check()
-
     machine.wait_for_unit("multi-user.target")
 
     with subtest("Check whether TPM device exists"):
diff --git a/nixos/tests/systemd-cryptenroll.nix b/nixos/tests/systemd-cryptenroll.nix
index 055ae7d1681f2..034aae1d5e955 100644
--- a/nixos/tests/systemd-cryptenroll.nix
+++ b/nixos/tests/systemd-cryptenroll.nix
@@ -8,47 +8,34 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     environment.systemPackages = [ pkgs.cryptsetup ];
     virtualisation = {
       emptyDiskImages = [ 512 ];
-      qemu.options = [
-        "-chardev socket,id=chrtpm,path=/tmp/swtpm-sock"
-        "-tpmdev emulator,id=tpm0,chardev=chrtpm"
-        "-device tpm-tis,tpmdev=tpm0"
-      ];
+      tpm.enable = true;
     };
   };
 
   testScript = ''
-    import subprocess
-    import tempfile
-
-    def start_swtpm(tpmstate):
-        subprocess.Popen(["${pkgs.swtpm}/bin/swtpm", "socket", "--tpmstate", "dir="+tpmstate, "--ctrl", "type=unixio,path=/tmp/swtpm-sock", "--log", "level=0", "--tpm2"])
-
-    with tempfile.TemporaryDirectory() as tpmstate:
-        start_swtpm(tpmstate)
-        machine.start()
-
-        # Verify the TPM device is available and accessible by systemd-cryptenroll
-        machine.succeed("test -e /dev/tpm0")
-        machine.succeed("test -e /dev/tpmrm0")
-        machine.succeed("systemd-cryptenroll --tpm2-device=list")
-
-        # Create LUKS partition
-        machine.succeed("echo -n lukspass | cryptsetup luksFormat -q /dev/vdb -")
-        # Enroll new LUKS key and bind it to Secure Boot state
-        # For more details on PASSWORD variable, check the following issue:
-        # https://github.com/systemd/systemd/issues/20955
-        machine.succeed("PASSWORD=lukspass systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/vdb")
-        # Add LUKS partition to /etc/crypttab to test auto unlock
-        machine.succeed("echo 'luks /dev/vdb - tpm2-device=auto' >> /etc/crypttab")
-        machine.shutdown()
-
-        start_swtpm(tpmstate)
-        machine.start()
-
-        # Test LUKS partition automatic unlock on boot
-        machine.wait_for_unit("systemd-cryptsetup@luks.service")
-        # Wipe TPM2 slot
-        machine.succeed("systemd-cryptenroll --wipe-slot=tpm2 /dev/vdb")
+    machine.start()
+
+    # Verify the TPM device is available and accessible by systemd-cryptenroll
+    machine.succeed("test -e /dev/tpm0")
+    machine.succeed("test -e /dev/tpmrm0")
+    machine.succeed("systemd-cryptenroll --tpm2-device=list")
+
+    # Create LUKS partition
+    machine.succeed("echo -n lukspass | cryptsetup luksFormat -q /dev/vdb -")
+    # Enroll new LUKS key and bind it to Secure Boot state
+    # For more details on PASSWORD variable, check the following issue:
+    # https://github.com/systemd/systemd/issues/20955
+    machine.succeed("PASSWORD=lukspass systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=7 /dev/vdb")
+    # Add LUKS partition to /etc/crypttab to test auto unlock
+    machine.succeed("echo 'luks /dev/vdb - tpm2-device=auto' >> /etc/crypttab")
+
+    machine.shutdown()
+    machine.start()
+
+    # Test LUKS partition automatic unlock on boot
+    machine.wait_for_unit("systemd-cryptsetup@luks.service")
+    # Wipe TPM2 slot
+    machine.succeed("systemd-cryptenroll --wipe-slot=tpm2 /dev/vdb")
   '';
 })
 
diff --git a/nixos/tests/systemd-initrd-bridge.nix b/nixos/tests/systemd-initrd-bridge.nix
new file mode 100644
index 0000000000000..f48a46ff2b939
--- /dev/null
+++ b/nixos/tests/systemd-initrd-bridge.nix
@@ -0,0 +1,63 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-initrd-bridge";
+  meta.maintainers = [ lib.maintainers.majiir ];
+
+  # Tests bridge interface configuration in systemd-initrd.
+  #
+  # The 'a' and 'b' nodes are connected to a 'bridge' node through different
+  # links. The 'bridge' node configures a bridge across them. It waits forever
+  # in initrd (stage 1) with networking enabled. 'a' and 'b' ping 'bridge' to
+  # test connectivity with the bridge interface. Then, 'a' pings 'b' to test
+  # the bridge itself.
+
+  nodes = {
+    bridge = { config, lib, ... }: {
+      boot.initrd.systemd.enable = true;
+      boot.initrd.network.enable = true;
+      boot.initrd.systemd.services.boot-blocker = {
+        before = [ "initrd.target" ];
+        wantedBy = [ "initrd.target" ];
+        script = "sleep infinity";
+        serviceConfig.Type = "oneshot";
+      };
+
+      networking.primaryIPAddress = "192.168.1.${toString config.virtualisation.test.nodeNumber}";
+
+      virtualisation.vlans = [ 1 2 ];
+      networking.bridges.br0.interfaces = [ "eth1" "eth2" ];
+
+      networking.interfaces = {
+        eth1.ipv4.addresses = lib.mkForce [];
+        eth2.ipv4.addresses = lib.mkForce [];
+        br0.ipv4.addresses = [{
+          address = config.networking.primaryIPAddress;
+          prefixLength = 24;
+        }];
+      };
+    };
+
+    a = {
+      virtualisation.vlans = [ 1 ];
+    };
+
+    b = { config, ... }: {
+      virtualisation.vlans = [ 2 ];
+      networking.primaryIPAddress = lib.mkForce "192.168.1.${toString config.virtualisation.test.nodeNumber}";
+      networking.interfaces.eth1.ipv4.addresses = lib.mkForce [{
+        address = config.networking.primaryIPAddress;
+        prefixLength = 24;
+      }];
+    };
+  };
+
+  testScript = ''
+    start_all()
+    a.wait_for_unit("network.target")
+    b.wait_for_unit("network.target")
+
+    a.succeed("ping -n -w 10 -c 1 bridge >&2")
+    b.succeed("ping -n -w 10 -c 1 bridge >&2")
+
+    a.succeed("ping -n -w 10 -c 1 b >&2")
+  '';
+})
diff --git a/nixos/tests/systemd-initrd-luks-tpm2.nix b/nixos/tests/systemd-initrd-luks-tpm2.nix
index d9dd9118a3a24..e292acfd1c5f9 100644
--- a/nixos/tests/systemd-initrd-luks-tpm2.nix
+++ b/nixos/tests/systemd-initrd-luks-tpm2.nix
@@ -9,7 +9,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       # Booting off the TPM2-encrypted device requires an available init script
       mountHostNixStore = true;
       useEFIBoot = true;
-      qemu.options = ["-chardev socket,id=chrtpm,path=/tmp/mytpm1/swtpm-sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"];
+      tpm.enable = true;
     };
     boot.loader.systemd-boot.enable = true;
 
@@ -33,29 +33,6 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   };
 
   testScript = ''
-    import subprocess
-    import os
-    import time
-
-
-    class Tpm:
-        def __init__(self):
-            os.mkdir("/tmp/mytpm1")
-            self.start()
-
-        def start(self):
-            self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm", "socket", "--tpmstate", "dir=/tmp/mytpm1", "--ctrl", "type=unixio,path=/tmp/mytpm1/swtpm-sock", "--log", "level=20", "--tpm2"])
-
-        def wait_for_death_then_restart(self):
-            while self.proc.poll() is None:
-                print("waiting for tpm to die")
-                time.sleep(1)
-            assert self.proc.returncode == 0
-            self.start()
-
-    tpm = Tpm()
-
-
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
     machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
@@ -66,8 +43,6 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     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-luks-unl0kr.nix b/nixos/tests/systemd-initrd-luks-unl0kr.nix
new file mode 100644
index 0000000000000..0658a098cfa2b
--- /dev/null
+++ b/nixos/tests/systemd-initrd-luks-unl0kr.nix
@@ -0,0 +1,75 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: let
+  passphrase = "secret";
+in {
+  name = "systemd-initrd-luks-unl0kr";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ tomfitzhenry ];
+  };
+
+  enableOCR = true;
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation = {
+      emptyDiskImages = [ 512 512 ];
+      useBootLoader = true;
+      mountHostNixStore = true;
+      useEFIBoot = true;
+      qemu.options = [
+        "-vga virtio"
+      ];
+    };
+    boot.loader.systemd-boot.enable = true;
+
+    boot.initrd.availableKernelModules = [
+      "evdev" # for entering pw
+      "bochs"
+    ];
+
+    environment.systemPackages = with pkgs; [ cryptsetup ];
+    boot.initrd = {
+      systemd = {
+        enable = true;
+        emergencyAccess = true;
+      };
+      unl0kr.enable = true;
+    };
+
+    specialisation.boot-luks.configuration = {
+      boot.initrd.luks.devices = lib.mkVMOverride {
+        # We have two disks and only type one password - key reuse is in place
+        cryptroot.device = "/dev/vdb";
+        cryptroot2.device = "/dev/vdc";
+      };
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
+      virtualisation.fileSystems."/".autoFormat = true;
+      # test mounting device unlocked in initrd after switching root
+      virtualisation.fileSystems."/cryptroot2".device = "/dev/mapper/cryptroot2";
+    };
+  };
+
+  testScript = ''
+    # Create encrypted volume
+    machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n ${passphrase} | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("echo -n ${passphrase} | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
+    machine.succeed("echo -n ${passphrase} | cryptsetup luksOpen   -q               /dev/vdc cryptroot2")
+    machine.succeed("mkfs.ext4 /dev/mapper/cryptroot2")
+
+    # Boot from the encrypted disk
+    machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
+    machine.succeed("sync")
+    machine.crash()
+
+    # Boot and decrypt the disk
+    machine.start()
+    machine.wait_for_text("Password required for booting")
+    machine.screenshot("prompt")
+    machine.send_chars("${passphrase}")
+    machine.screenshot("pw")
+    machine.send_chars("\n")
+    machine.wait_for_unit("multi-user.target")
+
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount"), "/dev/mapper/cryptroot do not appear in mountpoints list"
+    assert "/dev/mapper/cryptroot2 on /cryptroot2 type ext4" in machine.succeed("mount")
+  '';
+})
diff --git a/nixos/tests/systemd-initrd-modprobe.nix b/nixos/tests/systemd-initrd-modprobe.nix
index bf635a10d0e97..0f93492176b44 100644
--- a/nixos/tests/systemd-initrd-modprobe.nix
+++ b/nixos/tests/systemd-initrd-modprobe.nix
@@ -2,6 +2,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "systemd-initrd-modprobe";
 
   nodes.machine = { pkgs, ... }: {
+    testing.initrdBackdoor = true;
     boot.initrd.systemd.enable = true;
     boot.initrd.kernelModules = [ "loop" ]; # Load module in initrd.
     boot.extraModprobeConfig = ''
@@ -10,6 +11,12 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   };
 
   testScript = ''
+    machine.wait_for_unit("initrd.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"
+
+    # Make sure it sticks in stage 2
+    machine.switch_root()
     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-networkd-ssh.nix b/nixos/tests/systemd-initrd-networkd-ssh.nix
index 6aaa6c828f7bd..d4c168f40e29d 100644
--- a/nixos/tests/systemd-initrd-networkd-ssh.nix
+++ b/nixos/tests/systemd-initrd-networkd-ssh.nix
@@ -4,34 +4,16 @@ import ./make-test-python.nix ({ lib, ... }: {
 
   nodes = {
     server = { config, pkgs, ... }: {
-      environment.systemPackages = [ pkgs.cryptsetup ];
-      boot.loader.systemd-boot.enable = true;
-      boot.loader.timeout = 0;
-      virtualisation = {
-        emptyDiskImages = [ 4096 ];
-        useBootLoader = true;
-        # Booting off the encrypted disk requires an available init script from
-        # the Nix store
-        mountHostNixStore = true;
-        useEFIBoot = true;
-      };
-
-      specialisation.encrypted-root.configuration = {
-        virtualisation.rootDevice = "/dev/mapper/root";
-        virtualisation.fileSystems."/".autoFormat = true;
-        boot.initrd.luks.devices = lib.mkVMOverride {
-          root.device = "/dev/vdb";
-        };
-        boot.initrd.systemd.enable = true;
-        boot.initrd.network = {
+      testing.initrdBackdoor = true;
+      boot.initrd.systemd.enable = true;
+      boot.initrd.systemd.contents."/etc/msg".text = "foo";
+      boot.initrd.network = {
+        enable = true;
+        ssh = {
           enable = true;
-          ssh = {
-            enable = true;
-            authorizedKeys = [ (lib.readFile ./initrd-network-ssh/id_ed25519.pub) ];
-            port = 22;
-            # Terrible hack so it works with useBootLoader
-            hostKeys = [ { outPath = "${./initrd-network-ssh/ssh_host_ed25519_key}"; } ];
-          };
+          authorizedKeys = [ (lib.readFile ./initrd-network-ssh/id_ed25519.pub) ];
+          port = 22;
+          hostKeys = [ ./initrd-network-ssh/ssh_host_ed25519_key ];
         };
       };
     };
@@ -63,24 +45,16 @@ import ./make-test-python.nix ({ lib, ... }: {
         status, _ = client.execute("nc -z server 22")
         return status == 0
 
-    server.wait_for_unit("multi-user.target")
-    server.succeed(
-        "echo somepass | cryptsetup luksFormat --type=luks2 /dev/vdb",
-        "bootctl set-default nixos-generation-1-specialisation-encrypted-root.conf",
-        "sync",
-    )
-    server.shutdown()
-    server.start()
-
     client.wait_for_unit("network.target")
     with client.nested("waiting for SSH server to come up"):
         retry(ssh_is_up)
 
-    client.succeed(
-        "echo somepass | ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'systemd-tty-ask-password-agent' & exit"
+    msg = client.succeed(
+        "ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'cat /etc/msg'"
     )
+    assert "foo" in msg
 
+    server.switch_root()
     server.wait_for_unit("multi-user.target")
-    server.succeed("mount | grep '/dev/mapper/root on /'")
   '';
 })
diff --git a/nixos/tests/systemd-initrd-networkd.nix b/nixos/tests/systemd-initrd-networkd.nix
index 8376276d8f63d..691f4300d7a23 100644
--- a/nixos/tests/systemd-initrd-networkd.nix
+++ b/nixos/tests/systemd-initrd-networkd.nix
@@ -1,17 +1,40 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: {
-  name = "systemd-initrd-network";
-  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, lib ? pkgs.lib
+}:
 
-  nodes = let
-    mkFlushTest = flush: script: { ... }: {
-      boot.initrd.systemd.enable = true;
-      boot.initrd.network = {
-        enable = true;
-        flushBeforeStage2 = flush;
-      };
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  inherit (lib.maintainers) elvishjerricco;
+
+  common = {
+    boot.initrd.systemd = {
+      enable = true;
+      network.wait-online.timeout = 10;
+      network.wait-online.anyInterface = true;
+      targets.network-online.requiredBy = [ "initrd.target" ];
+      services.systemd-networkd-wait-online.requiredBy =
+        [ "network-online.target" ];
+      initrdBin = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+    };
+    testing.initrdBackdoor = true;
+    boot.initrd.network.enable = true;
+  };
+
+  mkFlushTest = flush: script: makeTest {
+    name = "systemd-initrd-network-${lib.optionalString (!flush) "no-"}flush";
+    meta.maintainers = [ elvishjerricco ];
+
+    nodes.machine = {
+      imports = [ common ];
+
+      boot.initrd.network.flushBeforeStage2 = flush;
       systemd.services.check-flush = {
         requiredBy = ["multi-user.target"];
-        before = ["network-pre.target" "multi-user.target"];
+        before = [ "network-pre.target" "multi-user.target" "shutdown.target" ];
+        conflicts = [ "shutdown.target" ];
         wants = ["network-pre.target"];
         unitConfig.DefaultDependencies = false;
         serviceConfig.Type = "oneshot";
@@ -19,57 +42,53 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         inherit script;
       };
     };
-  in {
-    basic = { ... }: {
-      boot.initrd.network.enable = true;
 
-      boot.initrd.systemd = {
-        enable = true;
-        # Enable network-online to fail the test in case of timeout
-        network.wait-online.timeout = 10;
-        network.wait-online.anyInterface = true;
-        targets.network-online.requiredBy = [ "initrd.target" ];
-        services.systemd-networkd-wait-online.requiredBy =
-          [ "network-online.target" ];
+    testScript = ''
+      machine.wait_for_unit("network-online.target")
+      machine.succeed(
+          "ip addr | grep 10.0.2.15",
+          "ping -c1 10.0.2.2",
+      )
+      machine.switch_root()
 
-          initrdBin = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
-          services.check = {
-            requiredBy = [ "initrd.target" ];
-            before = [ "initrd.target" ];
-            after = [ "network-online.target" ];
-            serviceConfig.Type = "oneshot";
-            path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
-            script = ''
-              ip addr | grep 10.0.2.15 || exit 1
-              ping -c1 10.0.2.2 || exit 1
-            '';
-          };
-      };
-    };
-
-    doFlush = mkFlushTest true ''
-      if ip addr | grep 10.0.2.15; then
-        echo "Network configuration survived switch-root; flushBeforeStage2 failed"
-        exit 1
-      fi
+      machine.wait_for_unit("multi-user.target")
     '';
+  };
+
+in {
+  basic = makeTest {
+    name = "systemd-initrd-network";
+    meta.maintainers = [ elvishjerricco ];
 
-    dontFlush = mkFlushTest false ''
-      if ! (ip addr | grep 10.0.2.15); then
-        echo "Network configuration didn't survive switch-root"
-        exit 1
-      fi
+    nodes.machine = common;
+
+    testScript = ''
+      machine.wait_for_unit("network-online.target")
+      machine.succeed(
+          "ip addr | grep 10.0.2.15",
+          "ping -c1 10.0.2.2",
+      )
+      machine.switch_root()
+
+      # Make sure the systemd-network user was set correctly in initrd
+      machine.wait_for_unit("multi-user.target")
+      machine.succeed("[ $(stat -c '%U,%G' /run/systemd/netif/links) = systemd-network,systemd-network ]")
+      machine.succeed("ip addr show >&2")
+      machine.succeed("ip route show >&2")
     '';
   };
 
-  testScript = ''
-    start_all()
-    basic.wait_for_unit("multi-user.target")
-    doFlush.wait_for_unit("multi-user.target")
-    dontFlush.wait_for_unit("multi-user.target")
-    # Make sure the systemd-network user was set correctly in initrd
-    basic.succeed("[ $(stat -c '%U,%G' /run/systemd/netif/links) = systemd-network,systemd-network ]")
-    basic.succeed("ip addr show >&2")
-    basic.succeed("ip route show >&2")
+  doFlush = mkFlushTest true ''
+    if ip addr | grep 10.0.2.15; then
+      echo "Network configuration survived switch-root; flushBeforeStage2 failed"
+      exit 1
+    fi
+  '';
+
+  dontFlush = mkFlushTest false ''
+    if ! (ip addr | grep 10.0.2.15); then
+      echo "Network configuration didn't survive switch-root"
+      exit 1
+    fi
   '';
-})
+}
diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix
index a6a22e9d48e06..2b7283a821939 100644
--- a/nixos/tests/systemd-initrd-simple.nix
+++ b/nixos/tests/systemd-initrd-simple.nix
@@ -2,16 +2,19 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "systemd-initrd-simple";
 
   nodes.machine = { pkgs, ... }: {
-    boot.initrd.systemd = {
-      enable = true;
-      emergencyAccess = true;
-    };
+    testing.initrdBackdoor = true;
+    boot.initrd.systemd.enable = true;
     virtualisation.fileSystems."/".autoResize = true;
   };
 
   testScript = ''
     import subprocess
 
+    with subtest("testing initrd backdoor"):
+        machine.wait_for_unit("initrd.target")
+        machine.succeed("systemctl status initrd-fs.target")
+        machine.switch_root()
+
     with subtest("handover to stage-2 systemd works"):
         machine.wait_for_unit("multi-user.target")
         machine.succeed("systemd-analyze | grep -q '(initrd)'")  # direct handover
@@ -37,6 +40,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
         subprocess.check_call(["qemu-img", "resize", "vm-state-machine/machine.qcow2", "+1G"])
 
         machine.start()
+        machine.switch_root()
         newAvail = machine.succeed("df --output=avail / | sed 1d")
 
         assert int(oldAvail) < int(newAvail), "File system did not grow"
diff --git a/nixos/tests/systemd-initrd-swraid.nix b/nixos/tests/systemd-initrd-swraid.nix
index d87170c925742..d00e67b5705ab 100644
--- a/nixos/tests/systemd-initrd-swraid.nix
+++ b/nixos/tests/systemd-initrd-swraid.nix
@@ -20,6 +20,9 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
         ARRAY /dev/md0 devices=/dev/vdb,/dev/vdc
       '';
     };
+    environment.etc."mdadm.conf".text = ''
+      MAILADDR test@example.com
+    '';
     boot.initrd = {
       systemd = {
         enable = true;
@@ -29,11 +32,14 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     };
 
     specialisation.boot-swraid.configuration.virtualisation.rootDevice = "/dev/disk/by-label/testraid";
+    # This protects against a regression. We do not have to switch to it.
+    # It's sufficient to trigger its evaluation.
+    specialisation.build-old-initrd.configuration.boot.initrd.systemd.enable = lib.mkForce false;
   };
 
   testScript = ''
     # Create RAID
-    machine.succeed("mdadm --create --force /dev/md0 -n 2 --level=raid0 /dev/vdb /dev/vdc")
+    machine.succeed("mdadm --create --force /dev/md0 -n 2 --level=raid1 /dev/vdb /dev/vdc --metadata=0.90")
     machine.succeed("mkfs.ext4 -L testraid /dev/md0")
     machine.succeed("mkdir -p /mnt && mount /dev/md0 /mnt && echo hello > /mnt/test && umount /mnt")
 
@@ -48,5 +54,13 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     assert "/dev/md0 on / type ext4" in machine.succeed("mount")
     assert "hello" in machine.succeed("cat /test")
     assert "md0" in machine.succeed("cat /proc/mdstat")
+
+    expected_config = """MAILADDR test@example.com
+
+    ARRAY /dev/md0 devices=/dev/vdb,/dev/vdc
+    """
+    got_config = machine.execute("cat /etc/mdadm.conf")[1]
+    assert expected_config == got_config, repr((expected_config, got_config))
+    machine.wait_for_unit("mdmonitor.service")
   '';
 })
diff --git a/nixos/tests/systemd-initrd-vlan.nix b/nixos/tests/systemd-initrd-vlan.nix
new file mode 100644
index 0000000000000..5060163a047d2
--- /dev/null
+++ b/nixos/tests/systemd-initrd-vlan.nix
@@ -0,0 +1,59 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-initrd-vlan";
+  meta.maintainers = [ lib.maintainers.majiir ];
+
+  # Tests VLAN interface configuration in systemd-initrd.
+  #
+  # Two nodes are configured for a tagged VLAN. (Note that they also still have
+  # their ordinary eth0 and eth1 interfaces, which are not VLAN-tagged.)
+  #
+  # The 'server' node waits forever in initrd (stage 1) with networking
+  # enabled. The 'client' node pings it to test network connectivity.
+
+  nodes = let
+    network = id: {
+      networking = {
+        vlans."eth1.10" = {
+          id = 10;
+          interface = "eth1";
+        };
+        interfaces."eth1.10" = {
+          ipv4.addresses = [{
+            address = "192.168.10.${id}";
+            prefixLength = 24;
+          }];
+        };
+      };
+    };
+  in {
+    # Node that will use initrd networking.
+    server = network "1" // {
+      boot.initrd.systemd.enable = true;
+      boot.initrd.network.enable = true;
+      boot.initrd.systemd.services.boot-blocker = {
+        before = [ "initrd.target" ];
+        wantedBy = [ "initrd.target" ];
+        script = "sleep infinity";
+        serviceConfig.Type = "oneshot";
+      };
+    };
+
+    # Node that will ping the server.
+    client = network "2";
+  };
+
+  testScript = ''
+    start_all()
+    client.wait_for_unit("network.target")
+
+    # Wait for the regular (untagged) interface to be up.
+    def server_is_up(_) -> bool:
+        status, _ = client.execute("ping -n -c 1 server >&2")
+        return status == 0
+    with client.nested("waiting for server to come up"):
+        retry(server_is_up)
+
+    # Try to ping the (tagged) VLAN interface.
+    client.succeed("ping -n -w 10 -c 1 192.168.10.1 >&2")
+  '';
+})
diff --git a/nixos/tests/systemd-journal-gateway.nix b/nixos/tests/systemd-journal-gateway.nix
new file mode 100644
index 0000000000000..1d20943f23880
--- /dev/null
+++ b/nixos/tests/systemd-journal-gateway.nix
@@ -0,0 +1,90 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "systemd-journal-gateway";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ minijackson raitobezarius ];
+  };
+
+  # Named client for coherence with the systemd-journal-upload test, and for
+  # certificate validation
+  nodes.client = {
+    services.journald.gateway = {
+      enable = true;
+      cert = "/run/secrets/client/cert.pem";
+      key = "/run/secrets/client/key.pem";
+      trust = "/run/secrets/ca.cert.pem";
+    };
+  };
+
+  testScript = ''
+    import json
+    import subprocess
+    import tempfile
+
+    tmpdir_o = tempfile.TemporaryDirectory()
+    tmpdir = tmpdir_o.name
+
+    def generate_pems(domain: str):
+      subprocess.run(
+        [
+          "${pkgs.minica}/bin/minica",
+          "--ca-key=ca.key.pem",
+          "--ca-cert=ca.cert.pem",
+          f"--domains={domain}",
+        ],
+        cwd=str(tmpdir),
+      )
+
+    with subtest("Creating keys and certificates"):
+      generate_pems("server")
+      generate_pems("client")
+
+    client.wait_for_unit("multi-user.target")
+
+    def copy_pem(file: str):
+      machine.copy_from_host(source=f"{tmpdir}/{file}", target=f"/run/secrets/{file}")
+      machine.succeed(f"chmod 644 /run/secrets/{file}")
+
+    with subtest("Copying keys and certificates"):
+      machine.succeed("mkdir -p /run/secrets/{client,server}")
+      copy_pem("server/cert.pem")
+      copy_pem("server/key.pem")
+      copy_pem("client/cert.pem")
+      copy_pem("client/key.pem")
+      copy_pem("ca.cert.pem")
+
+    client.wait_for_unit("multi-user.target")
+
+    curl = '${pkgs.curl}/bin/curl'
+    accept_json = '--header "Accept: application/json"'
+    cacert = '--cacert /run/secrets/ca.cert.pem'
+    cert = '--cert /run/secrets/server/cert.pem'
+    key = '--key /run/secrets/server/key.pem'
+    base_url = 'https://client:19531'
+
+    curl_cli = f"{curl} {accept_json} {cacert} {cert} {key} --fail"
+
+    machine_info = client.succeed(f"{curl_cli} {base_url}/machine")
+    assert json.loads(machine_info)["hostname"] == "client", "wrong machine name"
+
+    # The HTTP request should have started the gateway service, triggered by
+    # the .socket unit
+    client.wait_for_unit("systemd-journal-gatewayd.service")
+
+    identifier = "nixos-test"
+    message = "Hello from NixOS test infrastructure"
+
+    client.succeed(f"systemd-cat --identifier={identifier} <<< '{message}'")
+
+    # max-time is a workaround against a bug in systemd-journal-gatewayd where
+    # if TLS is enabled, the connection is never closed. Since it will timeout,
+    # we ignore the return code.
+    entries = client.succeed(
+        f"{curl_cli} --max-time 5 {base_url}/entries?SYSLOG_IDENTIFIER={identifier} || true"
+    )
+
+    # Number of entries should be only 1
+    added_entry = json.loads(entries)
+    assert added_entry["SYSLOG_IDENTIFIER"] == identifier and added_entry["MESSAGE"] == message, "journal entry does not correspond"
+  '';
+})
diff --git a/nixos/tests/systemd-journal-upload.nix b/nixos/tests/systemd-journal-upload.nix
new file mode 100644
index 0000000000000..0cbde379aee96
--- /dev/null
+++ b/nixos/tests/systemd-journal-upload.nix
@@ -0,0 +1,101 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "systemd-journal-upload";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ minijackson raitobezarius ];
+  };
+
+  nodes.server = { nodes, ... }: {
+    services.journald.remote = {
+      enable = true;
+      listen = "http";
+      settings.Remote = {
+        ServerCertificateFile = "/run/secrets/sever.cert.pem";
+        ServerKeyFile = "/run/secrets/sever.key.pem";
+        TrustedCertificateFile = "/run/secrets/ca.cert.pem";
+        Seal = true;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = [ nodes.server.services.journald.remote.port ];
+  };
+
+  nodes.client = { lib, nodes, ... }: {
+    services.journald.upload = {
+      enable = true;
+      settings.Upload = {
+        URL = "http://server:${toString nodes.server.services.journald.remote.port}";
+        ServerCertificateFile = "/run/secrets/client.cert.pem";
+        ServerKeyFile = "/run/secrets/client.key.pem";
+        TrustedCertificateFile = "/run/secrets/ca.cert.pem";
+      };
+    };
+
+    # Wait for the PEMs to arrive
+    systemd.services.systemd-journal-upload.wantedBy = lib.mkForce [];
+    systemd.paths.systemd-journal-upload = {
+      wantedBy = [ "default.target" ];
+      # This file must be copied last
+      pathConfig.PathExists = [ "/run/secrets/ca.cert.pem" ];
+    };
+  };
+
+  testScript = ''
+    import subprocess
+    import tempfile
+
+    tmpdir_o = tempfile.TemporaryDirectory()
+    tmpdir = tmpdir_o.name
+
+    def generate_pems(domain: str):
+      subprocess.run(
+        [
+          "${pkgs.minica}/bin/minica",
+          "--ca-key=ca.key.pem",
+          "--ca-cert=ca.cert.pem",
+          f"--domains={domain}",
+        ],
+        cwd=str(tmpdir),
+      )
+
+    with subtest("Creating keys and certificates"):
+      generate_pems("server")
+      generate_pems("client")
+
+    server.wait_for_unit("multi-user.target")
+    client.wait_for_unit("multi-user.target")
+
+    def copy_pems(machine: Machine, domain: str):
+      machine.succeed("mkdir /run/secrets")
+      machine.copy_from_host(
+        source=f"{tmpdir}/{domain}/cert.pem",
+        target=f"/run/secrets/{domain}.cert.pem",
+      )
+      machine.copy_from_host(
+        source=f"{tmpdir}/{domain}/key.pem",
+        target=f"/run/secrets/{domain}.key.pem",
+      )
+      # Should be last
+      machine.copy_from_host(
+        source=f"{tmpdir}/ca.cert.pem",
+        target="/run/secrets/ca.cert.pem",
+      )
+
+    with subtest("Copying keys and certificates"):
+      copy_pems(server, "server")
+      copy_pems(client, "client")
+
+    client.wait_for_unit("systemd-journal-upload.service")
+    # The journal upload should have started the remote service, triggered by
+    # the .socket unit
+    server.wait_for_unit("systemd-journal-remote.service")
+
+    identifier = "nixos-test"
+    message = "Hello from NixOS test infrastructure"
+
+    client.succeed(f"systemd-cat --identifier={identifier} <<< '{message}'")
+    server.wait_until_succeeds(
+      f"journalctl --file /var/log/journal/remote/remote-*.journal --identifier={identifier} | grep -F '{message}'"
+    )
+  '';
+})
diff --git a/nixos/tests/systemd-journal.nix b/nixos/tests/systemd-journal.nix
index d2063a3b9a44e..ad60c0f547a41 100644
--- a/nixos/tests/systemd-journal.nix
+++ b/nixos/tests/systemd-journal.nix
@@ -6,17 +6,11 @@ import ./make-test-python.nix ({ pkgs, ... }:
     maintainers = [ lewo ];
   };
 
-  nodes.machine = { pkgs, lib, ... }: {
-    services.journald.enableHttpGateway = true;
-  };
+  nodes.machine = { };
 
   testScript = ''
     machine.wait_for_unit("multi-user.target")
 
     machine.succeed("journalctl --grep=systemd")
-
-    machine.succeed(
-        "${pkgs.curl}/bin/curl -s localhost:19531/machine | ${pkgs.jq}/bin/jq -e '.hostname == \"machine\"'"
-    )
   '';
 })
diff --git a/nixos/tests/systemd-networkd-dhcpserver.nix b/nixos/tests/systemd-networkd-dhcpserver.nix
index cf0ccb7442118..665d8b5a05291 100644
--- a/nixos/tests/systemd-networkd-dhcpserver.nix
+++ b/nixos/tests/systemd-networkd-dhcpserver.nix
@@ -101,6 +101,9 @@ import ./make-test-python.nix ({pkgs, ...}: {
   };
   testScript = { ... }: ''
     start_all()
+
+    router.systemctl("start network-online.target")
+    client.systemctl("start network-online.target")
     router.wait_for_unit("systemd-networkd-wait-online.service")
     client.wait_for_unit("systemd-networkd-wait-online.service")
     client.wait_until_succeeds("ping -c 5 10.0.2.1")
diff --git a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
index 54f371e6c070f..1e55341657bdb 100644
--- a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
+++ b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
@@ -263,9 +263,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
           };
         };
       };
-
-      # make the network-online target a requirement, we wait for it in our test script
-      systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
     };
 
     # This is the client behind the router. We should be receiving router
@@ -278,9 +275,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         useNetworkd = true;
         useDHCP = false;
       };
-
-      # make the network-online target a requirement, we wait for it in our test script
-      systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
     };
   };
 
@@ -294,6 +288,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     # Since we only care about IPv6 that should not involve waiting for legacy
     # IP leases.
     client.start()
+    client.systemctl("start network-online.target")
     client.wait_for_unit("network-online.target")
 
     # the static address on the router should not be reachable
@@ -312,6 +307,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     isp.wait_for_unit("multi-user.target")
 
     # wait until the uplink interface has a good status
+    router.systemctl("start network-online.target")
     router.wait_for_unit("network-online.target")
     router.wait_until_succeeds("ping -6 -c1 2001:DB8::1")
 
diff --git a/nixos/tests/systemd-networkd.nix b/nixos/tests/systemd-networkd.nix
index 6c423f4140b1f..6b241b93d5118 100644
--- a/nixos/tests/systemd-networkd.nix
+++ b/nixos/tests/systemd-networkd.nix
@@ -65,7 +65,7 @@ let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: {
 in import ./make-test-python.nix ({pkgs, ... }: {
   name = "networkd";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ninjatrappeur ];
+    maintainers = [ picnoir ];
   };
   nodes = {
     node1 = { pkgs, ... }@attrs:
diff --git a/nixos/tests/systemd-nspawn.nix b/nixos/tests/systemd-nspawn.nix
index 1a4251ef069e8..b86762233d183 100644
--- a/nixos/tests/systemd-nspawn.nix
+++ b/nixos/tests/systemd-nspawn.nix
@@ -38,6 +38,7 @@ in {
     start_all()
 
     server.wait_for_unit("nginx.service")
+    client.systemctl("start network-online.target")
     client.wait_for_unit("network-online.target")
     client.succeed("machinectl pull-raw --verify=signature http://server/testimage.raw")
     client.succeed(
diff --git a/nixos/tests/systemd-repart.nix b/nixos/tests/systemd-repart.nix
index 22ea8fbd22711..3914d5b323977 100644
--- a/nixos/tests/systemd-repart.nix
+++ b/nixos/tests/systemd-repart.nix
@@ -29,16 +29,6 @@ let
       "+32M",
     ])
 
-    # Fix the GPT table by moving the backup table to the end of the enlarged
-    # disk image. This is necessary because we increased the size of the disk
-    # before. The disk needs to be a raw disk because sgdisk can only run on
-    # raw images.
-    subprocess.run([
-      "${pkgs.gptfdisk}/bin/sgdisk",
-      "--move-second-header",
-      tmp_disk_image.name,
-    ])
-
     # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
     os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
   '';
diff --git a/nixos/tests/systemd-sysusers-immutable.nix b/nixos/tests/systemd-sysusers-immutable.nix
new file mode 100644
index 0000000000000..42cbf84d175e4
--- /dev/null
+++ b/nixos/tests/systemd-sysusers-immutable.nix
@@ -0,0 +1,64 @@
+{ lib, ... }:
+
+let
+  rootPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
+  normaloPassword = "$y$j9T$3aiOV/8CADAK22OK2QT3/0$67OKd50Z4qTaZ8c/eRWHLIM.o3ujtC1.n9ysmJfv639";
+  newNormaloPassword = "mellow";
+in
+
+{
+
+  name = "activation-sysusers-immutable";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = {
+    systemd.sysusers.enable = true;
+    users.mutableUsers = false;
+
+    # Override the empty root password set by the test instrumentation
+    users.users.root.hashedPasswordFile = lib.mkForce null;
+    users.users.root.initialHashedPassword = rootPassword;
+    users.users.normalo = {
+      isNormalUser = true;
+      initialHashedPassword = normaloPassword;
+    };
+
+    specialisation.new-generation.configuration = {
+      users.users.new-normalo = {
+        isNormalUser = true;
+        initialPassword = newNormaloPassword;
+      };
+    };
+  };
+
+  testScript = ''
+    with subtest("Users are not created with systemd-sysusers"):
+      machine.fail("systemctl status systemd-sysusers.service")
+      machine.fail("ls /etc/sysusers.d")
+
+    with subtest("Correct mode on the password files"):
+      assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
+      assert machine.succeed("stat -c '%a' /etc/group") == "644\n"
+      assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n"
+      assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n"
+
+    with subtest("root user has correct password"):
+      print(machine.succeed("getent passwd root"))
+      assert "${rootPassword}" in machine.succeed("getent shadow root"), "root user password is not correct"
+
+    with subtest("normalo user is created"):
+      print(machine.succeed("getent passwd normalo"))
+      assert machine.succeed("stat -c '%U' /home/normalo") == "normalo\n"
+      assert "${normaloPassword}" in machine.succeed("getent shadow normalo"), "normalo user password is not correct"
+
+
+    machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+
+    with subtest("new-normalo user is created after switching to new generation"):
+      print(machine.succeed("getent passwd new-normalo"))
+      print(machine.succeed("getent shadow new-normalo"))
+      assert machine.succeed("stat -c '%U' /home/new-normalo") == "new-normalo\n"
+  '';
+}
diff --git a/nixos/tests/systemd-sysusers-mutable.nix b/nixos/tests/systemd-sysusers-mutable.nix
new file mode 100644
index 0000000000000..e69cfe23a59a1
--- /dev/null
+++ b/nixos/tests/systemd-sysusers-mutable.nix
@@ -0,0 +1,71 @@
+{ lib, ... }:
+
+let
+  rootPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
+  normaloPassword = "hello";
+  newNormaloPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
+in
+
+{
+
+  name = "activation-sysusers-mutable";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes.machine = { pkgs, ... }: {
+    systemd.sysusers.enable = true;
+    users.mutableUsers = true;
+
+    # Prerequisites
+    system.etc.overlay.enable = true;
+    boot.initrd.systemd.enable = true;
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    # Override the empty root password set by the test instrumentation
+    users.users.root.hashedPasswordFile = lib.mkForce null;
+    users.users.root.initialHashedPassword = rootPassword;
+    users.users.normalo = {
+      isNormalUser = true;
+      initialPassword = normaloPassword;
+    };
+
+    specialisation.new-generation.configuration = {
+      users.users.new-normalo = {
+        isNormalUser = true;
+        initialHashedPassword = newNormaloPassword;
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("systemd-sysusers.service")
+
+    with subtest("systemd-sysusers.service contains the credentials"):
+      sysusers_service = machine.succeed("systemctl cat systemd-sysusers.service")
+      print(sysusers_service)
+      assert "SetCredential=passwd.plaintext-password.normalo:${normaloPassword}" in sysusers_service
+
+    with subtest("Correct mode on the password files"):
+      assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
+      assert machine.succeed("stat -c '%a' /etc/group") == "644\n"
+      assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n"
+      assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n"
+
+    with subtest("root user has correct password"):
+      print(machine.succeed("getent passwd root"))
+      assert "${rootPassword}" in machine.succeed("getent shadow root"), "root user password is not correct"
+
+    with subtest("normalo user is created"):
+      print(machine.succeed("getent passwd normalo"))
+      assert machine.succeed("stat -c '%U' /home/normalo") == "normalo\n"
+
+
+    machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
+
+
+    with subtest("new-normalo user is created after switching to new generation"):
+      print(machine.succeed("getent passwd new-normalo"))
+      assert machine.succeed("stat -c '%U' /home/new-normalo") == "new-normalo\n"
+      assert "${newNormaloPassword}" in machine.succeed("getent shadow new-normalo"), "new-normalo user password is not correct"
+  '';
+}
diff --git a/nixos/tests/systemd-timesyncd-nscd-dnssec.nix b/nixos/tests/systemd-timesyncd-nscd-dnssec.nix
new file mode 100644
index 0000000000000..697dd824e3453
--- /dev/null
+++ b/nixos/tests/systemd-timesyncd-nscd-dnssec.nix
@@ -0,0 +1,61 @@
+# This test verifies that systemd-timesyncd can resolve the NTP server hostname when DNSSEC validation
+# fails even though it is enforced in the systemd-resolved settings. It is required in order to solve
+# the chicken-and-egg problem when DNSSEC validation needs the correct time to work, but to set the
+# correct time, we need to connect to an NTP server, which usually requires resolving its hostname.
+#
+# This test does the following:
+# - Sets up a DNS server (tinydns) listening on the eth1 ip addess, serving .ntp and fake.ntp records.
+# - Configures that DNS server as a resolver and enables DNSSEC in systemd-resolved settings.
+# - Configures systemd-timesyncd to use fake.ntp hostname as an NTP server.
+# - Performs a regular DNS lookup, to ensure it fails due to broken DNSSEC.
+# - Waits until systemd-timesyncd resolves fake.ntp by checking its debug output.
+#   Here, we don't expect systemd-timesyncd to connect and synchronize time because there is no NTP
+#   server running. For this test to succeed, we only need to ensure that systemd-timesyncd
+#   resolves the IP address of the fake.ntp host.
+
+import ./make-test-python.nix ({ pkgs, ... }:
+
+let
+  ntpHostname = "fake.ntp";
+  ntpIP = "192.0.2.1";
+in
+{
+  name = "systemd-timesyncd";
+  nodes.machine = { pkgs, lib, config, ... }:
+    let
+      eth1IP = (lib.head config.networking.interfaces.eth1.ipv4.addresses).address;
+    in
+    {
+      # Setup a local DNS server for the NTP domain on the eth1 IP address
+      services.tinydns = {
+        enable = true;
+        ip = eth1IP;
+        data = ''
+          .ntp:${eth1IP}
+          +.${ntpHostname}:${ntpIP}
+        '';
+      };
+
+      # Enable systemd-resolved with DNSSEC and use the local DNS as a name server
+      services.resolved.enable = true;
+      services.resolved.dnssec = "true";
+      networking.nameservers = [ eth1IP ];
+
+      # Configure systemd-timesyncd to use our NTP hostname
+      services.timesyncd.enable = lib.mkForce true;
+      services.timesyncd.servers = [ ntpHostname ];
+      services.timesyncd.extraConfig = ''
+        FallbackNTP=${ntpHostname}
+      '';
+
+      # The debug output is necessary to determine whether systemd-timesyncd successfully resolves our NTP hostname or not
+      systemd.services.systemd-timesyncd.environment.SYSTEMD_LOG_LEVEL = "debug";
+    };
+
+  testScript = ''
+    machine.wait_for_unit("tinydns.service")
+    machine.wait_for_unit("systemd-timesyncd.service")
+    machine.fail("resolvectl query ${ntpHostname}")
+    machine.wait_until_succeeds("journalctl -u systemd-timesyncd.service --grep='Resolved address ${ntpIP}:123 for ${ntpHostname}'")
+  '';
+})
diff --git a/nixos/tests/systemd-timesyncd.nix b/nixos/tests/systemd-timesyncd.nix
index 43abd36c47d97..02f49f49b8a58 100644
--- a/nixos/tests/systemd-timesyncd.nix
+++ b/nixos/tests/systemd-timesyncd.nix
@@ -15,12 +15,14 @@ in {
       # create the path that should be migrated by our activation script when
       # upgrading to a newer nixos version
       system.stateVersion = "19.03";
-      system.activationScripts.simulate-old-timesync-state-dir = lib.mkBefore ''
-        rm -f /var/lib/systemd/timesync
-        mkdir -p /var/lib/systemd /var/lib/private/systemd/timesync
-        ln -s /var/lib/private/systemd/timesync /var/lib/systemd/timesync
-        chown systemd-timesync: /var/lib/private/systemd/timesync
-      '';
+      systemd.tmpfiles.settings.systemd-timesyncd-test = {
+        "/var/lib/systemd/timesync".R = { };
+        "/var/lib/systemd/timesync".L.argument = "/var/lib/private/systemd/timesync";
+        "/var/lib/private/systemd/timesync".d = {
+          user = "systemd-timesync";
+          group = "systemd-timesync";
+        };
+      };
     });
   };
 
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index 3c36291b733d2..1a39cc73c8868 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -76,6 +76,17 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     # wait for user services
     machine.wait_for_unit("default.target", "alice")
 
+    with subtest("systemctl edit suggests --runtime"):
+        # --runtime is suggested when using `systemctl edit`
+        ret, out = machine.execute("systemctl edit testservice1.service 2>&1")
+        assert ret == 1
+        assert out.rstrip("\n") == "The unit-directory '/etc/systemd/system' is read-only on NixOS, so it's not possible to edit system-units directly. Use 'systemctl edit --runtime' instead."
+        # editing w/o `--runtime` is possible for user-services, however
+        # it's not possible because we're not in a tty when grepping
+        # (i.e. hacky way to ensure that the error from above doesn't appear here).
+        _, out = machine.execute("systemctl --user edit testservice2.service 2>&1")
+        assert out.rstrip("\n") == "Cannot edit units if not on a tty."
+
     # Regression test for https://github.com/NixOS/nixpkgs/issues/105049
     with subtest("systemd reads timezone database in /etc/zoneinfo"):
         timer = machine.succeed("TZ=UTC systemctl show --property=TimersCalendar oncalendar-test.timer")
@@ -169,7 +180,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
     # Do some IP traffic
     output_ping = machine.succeed(
-        "systemd-run --wait -- /run/wrappers/bin/ping -c 1 127.0.0.1 2>&1"
+        "systemd-run --wait -- ping -c 1 127.0.0.1 2>&1"
     )
 
     with subtest("systemd reports accounting data on system.slice"):
diff --git a/nixos/tests/systemtap.nix b/nixos/tests/systemtap.nix
new file mode 100644
index 0000000000000..5cd79d66e872b
--- /dev/null
+++ b/nixos/tests/systemtap.nix
@@ -0,0 +1,50 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}@args:
+
+with pkgs.lib;
+
+let
+  stapScript = pkgs.writeText "test.stp" ''
+    probe kernel.function("do_sys_poll") {
+      println("kernel function probe & println work")
+      exit()
+    }
+  '';
+
+  ## TODO shared infra with ../kernel-generic.nix
+  testsForLinuxPackages = linuxPackages: (import ./make-test-python.nix ({ pkgs, ... }: {
+    name = "kernel-${linuxPackages.kernel.version}";
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ bendlas ];
+    };
+
+    nodes.machine = { ... }:
+      {
+        boot.kernelPackages = linuxPackages;
+        programs.systemtap.enable = true;
+      };
+
+    testScript =
+      ''
+        with subtest("Capture stap ouput"):
+            output = machine.succeed("stap ${stapScript} 2>&1")
+
+        with subtest("Ensure that expected output from stap script is there"):
+            assert "kernel function probe & println work\n" == output, "kernel function probe & println work\n != " + output
+      '';
+  }) args);
+
+  ## TODO shared infra with ../kernel-generic.nix
+  kernels = {
+    inherit (pkgs.linuxKernel.packageAliases) linux_default linux_latest;
+  };
+
+in mapAttrs (_: lP: testsForLinuxPackages lP) kernels // {
+  passthru = {
+    inherit testsForLinuxPackages;
+
+    testsForKernel = kernel: testsForLinuxPackages (pkgs.linuxPackagesFor kernel);
+  };
+}
diff --git a/nixos/tests/tandoor-recipes.nix b/nixos/tests/tandoor-recipes.nix
index 54456238fe634..18beaac6f062c 100644
--- a/nixos/tests/tandoor-recipes.nix
+++ b/nixos/tests/tandoor-recipes.nix
@@ -3,10 +3,8 @@ import ./make-test-python.nix ({ lib, ... }: {
   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";
@@ -21,7 +19,7 @@ import ./make-test-python.nix ({ lib, ... }: {
       ensureUsers = [
         {
           name = "tandoor_recipes";
-          ensurePermissions."DATABASE tandoor_recipes" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/tests/tang.nix b/nixos/tests/tang.nix
new file mode 100644
index 0000000000000..10486a9feb8cf
--- /dev/null
+++ b/nixos/tests/tang.nix
@@ -0,0 +1,81 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "tang";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ jfroche ];
+  };
+
+  nodes.server =
+    { config
+    , pkgs
+    , modulesPath
+    , ...
+    }: {
+      imports = [
+        "${modulesPath}/../tests/common/auto-format-root-device.nix"
+      ];
+      virtualisation = {
+        emptyDiskImages = [ 512 ];
+        useBootLoader = true;
+        useEFIBoot = true;
+        # This requires to have access
+        # to a host Nix store as
+        # the new root device is /dev/vdb
+        # an empty 512MiB drive, containing no Nix store.
+        mountHostNixStore = true;
+      };
+
+      boot.loader.systemd-boot.enable = true;
+
+      networking.interfaces.eth1.ipv4.addresses = [
+        { address = "192.168.0.1"; prefixLength = 24; }
+      ];
+
+      environment.systemPackages = with pkgs; [ clevis tang cryptsetup ];
+      services.tang = {
+        enable = true;
+        ipAddressAllow = [ "127.0.0.1/32" ];
+      };
+    };
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("sockets.target")
+
+    with subtest("Check keys are generated"):
+      machine.wait_until_succeeds("curl -v http://127.0.0.1:7654/adv")
+      key = machine.wait_until_succeeds("tang-show-keys 7654")
+
+    with subtest("Check systemd access list"):
+      machine.succeed("ping -c 3 192.168.0.1")
+      machine.fail("curl -v --connect-timeout 3 http://192.168.0.1:7654/adv")
+
+    with subtest("Check basic encrypt and decrypt message"):
+      machine.wait_until_succeeds(f"""echo 'Hello World' | clevis encrypt tang '{{ "url": "http://127.0.0.1:7654", "thp":"{key}"}}' > /tmp/encrypted""")
+      decrypted = machine.wait_until_succeeds("clevis decrypt < /tmp/encrypted")
+      assert decrypted.strip() == "Hello World"
+      machine.wait_until_succeeds("tang-show-keys 7654")
+
+    with subtest("Check encrypt and decrypt disk"):
+      machine.succeed("cryptsetup luksFormat --force-password --batch-mode /dev/vdb <<<'password'")
+      machine.succeed(f"""clevis luks bind -s1 -y -f -d /dev/vdb tang '{{ "url": "http://127.0.0.1:7654", "thp":"{key}" }}' <<< 'password' """)
+      clevis_luks = machine.succeed("clevis luks list -d /dev/vdb")
+      assert clevis_luks.strip() == """1: tang '{"url":"http://127.0.0.1:7654"}'"""
+      machine.succeed("clevis luks unlock -d /dev/vdb")
+      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")
+      machine.succeed("clevis luks unlock -d /dev/vdb")
+      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")
+      # without tang available, unlock should fail
+      machine.succeed("systemctl stop tangd.socket")
+      machine.fail("clevis luks unlock -d /dev/vdb")
+      machine.succeed("systemctl start tangd.socket")
+
+    with subtest("Rotate server keys"):
+      machine.succeed("${pkgs.tang}/libexec/tangd-rotate-keys -d /var/lib/tang")
+      machine.succeed("clevis luks unlock -d /dev/vdb")
+      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")
+
+    with subtest("Test systemd service security"):
+        output = machine.succeed("systemd-analyze security tangd@.service")
+        machine.log(output)
+        assert output[-9:-1] == "SAFE :-}"
+  '';
+})
diff --git a/nixos/tests/tayga.nix b/nixos/tests/tayga.nix
index 44974f6efea83..4aade67d74d0d 100644
--- a/nixos/tests/tayga.nix
+++ b/nixos/tests/tayga.nix
@@ -206,6 +206,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
   testScript = ''
     # start client and server
     for machine in client, server:
+      machine.systemctl("start network-online.target")
       machine.wait_for_unit("network-online.target")
       machine.log(machine.execute("ip addr")[1])
       machine.log(machine.execute("ip route")[1])
@@ -214,6 +215,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
     # test systemd-networkd and nixos-scripts based router
     for router in router_systemd, router_nixos:
       router.start()
+      router.systemctl("start network-online.target")
       router.wait_for_unit("network-online.target")
       router.wait_for_unit("tayga.service")
       router.log(machine.execute("ip addr")[1])
diff --git a/nixos/tests/teleport.nix b/nixos/tests/teleport.nix
index cdf762b128448..d68917c6c7acb 100644
--- a/nixos/tests/teleport.nix
+++ b/nixos/tests/teleport.nix
@@ -9,7 +9,8 @@ with import ../lib/testing-python.nix { inherit system pkgs; };
 let
   packages = with pkgs; {
     "default" = teleport;
-    "11" = teleport_11;
+    "12" = teleport_12;
+    "13" = teleport_13;
   };
 
   minimal = package: {
diff --git a/nixos/tests/terminal-emulators.nix b/nixos/tests/terminal-emulators.nix
index 6d76cc8e57412..3c1188ca88c99 100644
--- a/nixos/tests/terminal-emulators.nix
+++ b/nixos/tests/terminal-emulators.nix
@@ -23,9 +23,8 @@ with pkgs.lib;
 let tests = {
       alacritty.pkg = p: p.alacritty;
 
-      # times out after spending many hours
-      #contour.pkg = p: p.contour;
-      #contour.cmd = "contour $command";
+      contour.pkg = p: p.contour;
+      contour.cmd = "contour early-exit-threshold 0 execute $command";
 
       cool-retro-term.pkg = p: p.cool-retro-term;
       cool-retro-term.colourTest = false; # broken by gloss effect
@@ -62,6 +61,8 @@ let tests = {
 
       konsole.pkg = p: p.plasma5Packages.konsole;
 
+      lomiri-terminal-app.pkg = p: p.lomiri.lomiri-terminal-app;
+
       lxterminal.pkg = p: p.lxterminal;
 
       mate-terminal.pkg = p: p.mate.mate-terminal;
@@ -76,6 +77,7 @@ let tests = {
 
       rio.pkg = p: p.rio;
       rio.cmd = "rio -e $command";
+      rio.colourTest = false; # the rendering is changing too much so colors change every release.
 
       roxterm.pkg = p: p.roxterm;
       roxterm.cmd = "roxterm -e $command";
diff --git a/nixos/tests/timescaledb.nix b/nixos/tests/timescaledb.nix
index 00a7f9af09fb8..ba0a3cec6076f 100644
--- a/nixos/tests/timescaledb.nix
+++ b/nixos/tests/timescaledb.nix
@@ -52,7 +52,7 @@ let
         services.postgresql = {
           enable = true;
           package = postgresql-package;
-          extraPlugins = with postgresql-package.pkgs; [
+          extraPlugins = ps: with ps; [
             timescaledb
             timescaledb_toolkit
           ];
diff --git a/nixos/tests/tinyproxy.nix b/nixos/tests/tinyproxy.nix
new file mode 100644
index 0000000000000..b8448d4c23b66
--- /dev/null
+++ b/nixos/tests/tinyproxy.nix
@@ -0,0 +1,20 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "tinyproxy";
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.tinyproxy = {
+      enable = true;
+      settings = {
+        Listen = "127.0.0.1";
+        Port = 8080;
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("tinyproxy.service")
+    machine.wait_for_open_port(8080)
+
+    machine.succeed('curl -s http://localhost:8080 |grep -i tinyproxy')
+  '';
+})
diff --git a/nixos/tests/tinywl.nix b/nixos/tests/tinywl.nix
index 411cdb1f64192..9199866b57af7 100644
--- a/nixos/tests/tinywl.nix
+++ b/nixos/tests/tinywl.nix
@@ -16,6 +16,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
         systemPackages = with pkgs; [ tinywl foot wayland-utils ];
       };
 
+      hardware.opengl.enable = true;
+
       # Automatically start TinyWL when logging in on tty1:
       programs.bash.loginShellInit = ''
         if [ "$(tty)" = "/dev/tty1" ]; then
diff --git a/nixos/tests/tomcat.nix b/nixos/tests/tomcat.nix
index 4cfb3cc5a7d84..df5cb033b78f0 100644
--- a/nixos/tests/tomcat.nix
+++ b/nixos/tests/tomcat.nix
@@ -1,21 +1,27 @@
-import ./make-test-python.nix ({ pkgs, ... }:
-
-{
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
   name = "tomcat";
+  meta.maintainers = [ lib.maintainers.anthonyroussel ];
 
   nodes.machine = { pkgs, ... }: {
-    services.tomcat.enable = true;
+    services.tomcat = {
+      enable = true;
+      axis2.enable = true;
+    };
   };
 
   testScript = ''
     machine.wait_for_unit("tomcat.service")
     machine.wait_for_open_port(8080)
     machine.wait_for_file("/var/tomcat/webapps/examples");
+
+    machine.succeed(
+        "curl -sS --fail http://localhost:8080/examples/servlets/servlet/HelloWorldExample | grep 'Hello World!'"
+    )
     machine.succeed(
-        "curl --fail http://localhost:8080/examples/servlets/servlet/HelloWorldExample | grep 'Hello World!'"
+        "curl -sS --fail http://localhost:8080/examples/jsp/jsp2/simpletag/hello.jsp | grep 'Hello, world!'"
     )
     machine.succeed(
-        "curl --fail http://localhost:8080/examples/jsp/jsp2/simpletag/hello.jsp | grep 'Hello, world!'"
+        "curl -sS --fail http://localhost:8080/axis2/axis2-web/HappyAxis.jsp | grep 'Found Axis2'"
     )
   '';
 })
diff --git a/nixos/tests/tracee.nix b/nixos/tests/tracee.nix
index 8ec86ef091ef7..3dadc0f9fdb33 100644
--- a/nixos/tests/tracee.nix
+++ b/nixos/tests/tracee.nix
@@ -43,6 +43,10 @@ import ./make-test-python.nix ({ pkgs, ... }: {
             mv $GOPATH/tracee-integration $out/bin/
           '';
           doInstallCheck = false;
+
+          meta = oa.meta // {
+            outputsToInstall = [];
+          };
         }))
       ];
     };
diff --git a/nixos/tests/trafficserver.nix b/nixos/tests/trafficserver.nix
index e4557c6c50e54..94d0e4dd926e9 100644
--- a/nixos/tests/trafficserver.nix
+++ b/nixos/tests/trafficserver.nix
@@ -104,6 +104,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     ats.wait_for_open_port(80)
     httpbin.wait_for_unit("httpbin")
     httpbin.wait_for_open_port(80)
+    client.systemctl("start network-online.target")
     client.wait_for_unit("network-online.target")
 
     with subtest("Traffic Server is running"):
diff --git a/nixos/tests/transmission.nix b/nixos/tests/transmission.nix
index b69ddd84d009a..03fc9a421510a 100644
--- a/nixos/tests/transmission.nix
+++ b/nixos/tests/transmission.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ pkgs, ...} : {
+import ./make-test-python.nix ({ pkgs, transmission, ... }: {
   name = "transmission";
   meta = with pkgs.lib.maintainers; {
     maintainers = [ coconnor ];
@@ -12,6 +12,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
     security.apparmor.enable = true;
 
     services.transmission.enable = true;
+    services.transmission.package = transmission;
   };
 
   testScript =
diff --git a/nixos/tests/tsja.nix b/nixos/tests/tsja.nix
new file mode 100644
index 0000000000000..f34358ff3f5f3
--- /dev/null
+++ b/nixos/tests/tsja.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} : {
+  name = "tsja";
+  meta = {
+    maintainers = with lib.maintainers; [ chayleaf ];
+  };
+
+  nodes = {
+    master =
+      { config, ... }:
+
+      {
+        services.postgresql = {
+          enable = true;
+          extraPlugins = ps: with ps; [
+            tsja
+          ];
+        };
+      };
+  };
+
+  testScript = ''
+    start_all()
+    master.wait_for_unit("postgresql")
+    master.succeed("sudo -u postgres psql -f /run/current-system/sw/share/postgresql/extension/libtsja_dbinit.sql")
+    # make sure "日本語" is parsed as a separate lexeme
+    master.succeed("""
+      sudo -u postgres \\
+        psql -c "SELECT * FROM ts_debug('japanese', 'PostgreSQLで日本語のテキスト検索ができます。')" \\
+          | grep "{日本語}"
+    """)
+  '';
+})
diff --git a/nixos/tests/tsm-client-gui.nix b/nixos/tests/tsm-client-gui.nix
index e11501da53d0c..c9632546db6ef 100644
--- a/nixos/tests/tsm-client-gui.nix
+++ b/nixos/tests/tsm-client-gui.nix
@@ -18,9 +18,9 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       defaultServername = "testserver";
       servers.testserver = {
         # 192.0.0.8 is a "dummy address" according to RFC 7600
-        server = "192.0.0.8";
-        node = "SOME-NODE";
-        passwdDir = "/tmp";
+        tcpserveraddress = "192.0.0.8";
+        nodename = "SOME-NODE";
+        passworddir = "/tmp";
       };
     };
   };
diff --git a/nixos/tests/typesense.nix b/nixos/tests/typesense.nix
index 4f07a2e194be8..87ed248257ea0 100644
--- a/nixos/tests/typesense.nix
+++ b/nixos/tests/typesense.nix
@@ -18,6 +18,7 @@ in {
   testScript = ''
     machine.wait_for_unit("typesense.service")
     machine.wait_for_open_port(${toString testPort})
-    assert machine.succeed("curl --fail http://localhost:${toString testPort}/health") == '{"ok":true}'
+    # After waiting for the port, typesense still hasn't initialized the database, so wait until we can connect successfully
+    assert machine.wait_until_succeeds("curl --fail http://localhost:${toString testPort}/health") == '{"ok":true}'
   '';
 })
diff --git a/nixos/tests/udisks2.nix b/nixos/tests/udisks2.nix
index 6afb200f85664..8cc148750c7bc 100644
--- a/nixos/tests/udisks2.nix
+++ b/nixos/tests/udisks2.nix
@@ -32,6 +32,9 @@ in
     ''
       import lzma
 
+      machine.systemctl("start udisks2")
+      machine.wait_for_unit("udisks2.service")
+
       with lzma.open(
           "${stick}"
       ) as data, open(machine.state_dir / "usbstick.img", "wb") as stick:
diff --git a/nixos/tests/ulogd.nix b/nixos/tests/ulogd.nix
deleted file mode 100644
index d351fdae79836..0000000000000
--- a/nixos/tests/ulogd.nix
+++ /dev/null
@@ -1,82 +0,0 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: {
-  name = "ulogd";
-
-  meta.maintainers = with lib.maintainers; [ p-h ];
-
-  nodes.machine = { ... }: {
-    networking.firewall.enable = false;
-    networking.nftables.enable = true;
-    networking.nftables.ruleset = ''
-      table inet filter {
-        chain input {
-          type filter hook input priority 0;
-          log group 2 accept
-        }
-
-        chain output {
-          type filter hook output priority 0; policy accept;
-          log group 2 accept
-        }
-
-        chain forward {
-          type filter hook forward priority 0; policy drop;
-          log group 2 accept
-        }
-
-      }
-    '';
-    services.ulogd = {
-      enable = true;
-      settings = {
-        global = {
-          logfile = "/var/log/ulogd.log";
-          stack = "log1:NFLOG,base1:BASE,pcap1:PCAP";
-        };
-
-        log1.group = 2;
-
-        pcap1 = {
-          file = "/var/log/ulogd.pcap";
-          sync = 1;
-        };
-      };
-    };
-
-    environment.systemPackages = with pkgs; [
-      tcpdump
-    ];
-  };
-
-  testScript = ''
-    start_all()
-    machine.wait_for_unit("ulogd.service")
-    machine.wait_for_unit("network-online.target")
-
-    with subtest("Ulogd is running"):
-        machine.succeed("pgrep ulogd >&2")
-
-    # All packets show up twice in the logs
-    with subtest("Logs are collected"):
-        machine.succeed("ping -f 127.0.0.1 -c 5 >&2")
-        machine.succeed("sleep 2")
-        machine.wait_until_succeeds("du /var/log/ulogd.pcap >&2")
-        _, echo_request_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 8 and host 127.0.0.1")
-        expected, actual = 5*2, len(echo_request_packets.splitlines())
-        assert expected == actual, f"Expected {expected} packets, got: {actual}"
-        _, echo_reply_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 0 and host 127.0.0.1")
-        expected, actual = 5*2, len(echo_reply_packets.splitlines())
-        assert expected == actual, f"Expected {expected} packets, got: {actual}"
-
-    with subtest("Reloading service reopens log file"):
-        machine.succeed("mv /var/log/ulogd.pcap /var/log/old_ulogd.pcap")
-        machine.succeed("systemctl reload ulogd.service")
-        machine.succeed("ping -f 127.0.0.1 -c 5 >&2")
-        machine.succeed("sleep 2")
-        _, echo_request_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 8 and host 127.0.0.1")
-        expected, actual = 5*2, len(echo_request_packets.splitlines())
-        assert expected == actual, f"Expected {expected} packets, got: {actual}"
-        _, echo_reply_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 0 and host 127.0.0.1")
-        expected, actual = 5*2, len(echo_reply_packets.splitlines())
-        assert expected == actual, f"Expected {expected} packets, got: {actual}"
-  '';
-})
diff --git a/nixos/tests/ulogd/ulogd.nix b/nixos/tests/ulogd/ulogd.nix
new file mode 100644
index 0000000000000..0fa92229a1005
--- /dev/null
+++ b/nixos/tests/ulogd/ulogd.nix
@@ -0,0 +1,56 @@
+import ../make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "ulogd";
+
+  meta.maintainers = with lib.maintainers; [ p-h ];
+
+  nodes.machine = { ... }: {
+    networking.firewall.enable = false;
+    networking.nftables.enable = true;
+    networking.nftables.ruleset = ''
+      table inet filter {
+        chain input {
+          type filter hook input priority 0;
+          icmp type { echo-request, echo-reply } log group 2 accept
+        }
+
+        chain output {
+          type filter hook output priority 0; policy accept;
+          icmp type { echo-request, echo-reply } log group 2 accept
+        }
+
+        chain forward {
+          type filter hook forward priority 0; policy drop;
+        }
+
+      }
+    '';
+    services.ulogd = {
+      enable = true;
+      settings = {
+        global = {
+          logfile = "/var/log/ulogd.log";
+          stack = [
+            "log1:NFLOG,base1:BASE,ifi1:IFINDEX,ip2str1:IP2STR,print1:PRINTPKT,emu1:LOGEMU"
+            "log1:NFLOG,base1:BASE,pcap1:PCAP"
+          ];
+        };
+
+        log1.group = 2;
+
+        pcap1 = {
+          sync = 1;
+          file = "/var/log/ulogd.pcap";
+        };
+
+        emu1 = {
+          sync = 1;
+          file = "/var/log/ulogd_pkts.log";
+        };
+      };
+    };
+
+    environment.systemPackages = with pkgs; [ tcpdump ];
+  };
+
+  testScript = lib.readFile ./ulogd.py;
+})
diff --git a/nixos/tests/ulogd/ulogd.py b/nixos/tests/ulogd/ulogd.py
new file mode 100644
index 0000000000000..76a8d0c6e24a3
--- /dev/null
+++ b/nixos/tests/ulogd/ulogd.py
@@ -0,0 +1,49 @@
+start_all()
+machine.wait_for_unit("ulogd.service")
+machine.systemctl("start network-online.target")
+machine.wait_for_unit("network-online.target")
+
+with subtest("Ulogd is running"):
+    machine.succeed("pgrep ulogd >&2")
+
+# All packets show up twice in the logs
+with subtest("Logs are collected"):
+    machine.succeed("ping -f 127.0.0.1 -c 5 >&2")
+    machine.succeed("sleep 2")
+    machine.wait_until_succeeds("du /var/log/ulogd.pcap")
+    _, echo_request_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 8 and host 127.0.0.1")
+    expected, actual = 5 * 2, len(echo_request_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP request packets from pcap, got: {actual}"
+    _, echo_reply_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 0 and host 127.0.0.1")
+    expected, actual = 5 * 2, len(echo_reply_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP reply packets from pcap, got: {actual}"
+
+    machine.wait_until_succeeds("du /var/log/ulogd_pkts.log")
+    _, echo_request_packets = machine.execute("grep TYPE=8 /var/log/ulogd_pkts.log")
+    expected, actual = 5 * 2, len(echo_request_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP request packets from logfile, got: {actual}"
+    _, echo_reply_packets = machine.execute("grep TYPE=0 /var/log/ulogd_pkts.log")
+    expected, actual = 5 * 2, len(echo_reply_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP reply packets from logfile, got: {actual}"
+
+with subtest("Reloading service reopens log file"):
+    machine.succeed("mv /var/log/ulogd.pcap /var/log/old_ulogd.pcap")
+    machine.succeed("mv /var/log/ulogd_pkts.log /var/log/old_ulogd_pkts.log")
+    machine.succeed("systemctl reload ulogd.service")
+    machine.succeed("ping -f 127.0.0.1 -c 5 >&2")
+    machine.succeed("sleep 2")
+    machine.wait_until_succeeds("du /var/log/ulogd.pcap")
+    _, echo_request_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 8 and host 127.0.0.1")
+    expected, actual = 5 * 2, len(echo_request_packets.splitlines())
+    assert expected == actual, f"Expected {expected} packets, got: {actual}"
+    _, echo_reply_packets = machine.execute("tcpdump -r /var/log/ulogd.pcap icmp[0] == 0 and host 127.0.0.1")
+    expected, actual = 5 * 2, len(echo_reply_packets.splitlines())
+    assert expected == actual, f"Expected {expected} packets, got: {actual}"
+
+    machine.wait_until_succeeds("du /var/log/ulogd_pkts.log")
+    _, echo_request_packets = machine.execute("grep TYPE=8 /var/log/ulogd_pkts.log")
+    expected, actual = 5 * 2, len(echo_request_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP request packets from logfile, got: {actual}"
+    _, echo_reply_packets = machine.execute("grep TYPE=0 /var/log/ulogd_pkts.log")
+    expected, actual = 5 * 2, len(echo_reply_packets.splitlines())
+    assert expected == actual, f"Expected {expected} ICMP reply packets from logfile, got: {actual}"
diff --git a/nixos/tests/unbound.nix b/nixos/tests/unbound.nix
index f6732390b4347..39a01259edeb5 100644
--- a/nixos/tests/unbound.nix
+++ b/nixos/tests/unbound.nix
@@ -106,8 +106,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
               {
                 name = ".";
                 forward-addr = [
-                  (lib.head nodes.authoritative.config.networking.interfaces.eth1.ipv6.addresses).address
-                  (lib.head nodes.authoritative.config.networking.interfaces.eth1.ipv4.addresses).address
+                  (lib.head nodes.authoritative.networking.interfaces.eth1.ipv6.addresses).address
+                  (lib.head nodes.authoritative.networking.interfaces.eth1.ipv4.addresses).address
                 ];
               }
             ];
@@ -168,8 +168,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
           "unbound-extra1.conf".text = ''
             forward-zone:
             name: "example.local."
-            forward-addr: ${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv6.addresses).address}
-            forward-addr: ${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv4.addresses).address}
+            forward-addr: ${(lib.head nodes.resolver.networking.interfaces.eth1.ipv6.addresses).address}
+            forward-addr: ${(lib.head nodes.resolver.networking.interfaces.eth1.ipv4.addresses).address}
           '';
           "unbound-extra2.conf".text = ''
             auth-zone:
@@ -187,8 +187,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       client = { lib, nodes, ... }: {
         imports = [ common ];
         networking.nameservers = [
-          (lib.head nodes.resolver.config.networking.interfaces.eth1.ipv6.addresses).address
-          (lib.head nodes.resolver.config.networking.interfaces.eth1.ipv4.addresses).address
+          (lib.head nodes.resolver.networking.interfaces.eth1.ipv6.addresses).address
+          (lib.head nodes.resolver.networking.interfaces.eth1.ipv4.addresses).address
         ];
         networking.interfaces.eth1.ipv4.addresses = [
           { address = "192.168.0.10"; prefixLength = 24; }
@@ -276,7 +276,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
       resolver.wait_for_unit("multi-user.target")
 
       with subtest("client should be able to query the resolver"):
-          test(client, ["${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv6.addresses).address}", "${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv4.addresses).address}"], doh=True)
+          test(client, ["${(lib.head nodes.resolver.networking.interfaces.eth1.ipv6.addresses).address}", "${(lib.head nodes.resolver.networking.interfaces.eth1.ipv4.addresses).address}"], doh=True)
 
       # discard the client we do not need anymore
       client.shutdown()
@@ -298,7 +298,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
           ).strip()
 
           # Thank you black! Can't really break this line into a readable version.
-          expected = "example.local. IN forward ${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv6.addresses).address} ${(lib.head nodes.resolver.config.networking.interfaces.eth1.ipv4.addresses).address}"
+          expected = "example.local. IN forward ${(lib.head nodes.resolver.networking.interfaces.eth1.ipv6.addresses).address} ${(lib.head nodes.resolver.networking.interfaces.eth1.ipv4.addresses).address}"
           assert out == expected, f"Expected `{expected}` but got `{out}` instead."
           local_resolver.fail("sudo -u unauthorizeduser -- unbound-control list_forwards")
 
diff --git a/nixos/tests/upnp.nix b/nixos/tests/upnp.nix
index af7cc1fe24130..93bc08f752ce3 100644
--- a/nixos/tests/upnp.nix
+++ b/nixos/tests/upnp.nix
@@ -5,7 +5,7 @@
 # this succeeds an external client will try to connect to the port
 # mapping.
 
-import ./make-test-python.nix ({ pkgs, ... }:
+import ./make-test-python.nix ({ pkgs, useNftables, ... }:
 
 let
   internalRouterAddress = "192.168.3.1";
@@ -27,6 +27,7 @@ in
           networking.nat.enable = true;
           networking.nat.internalInterfaces = [ "eth2" ];
           networking.nat.externalInterface = "eth1";
+          networking.nftables.enable = useNftables;
           networking.firewall.enable = true;
           networking.firewall.trustedInterfaces = [ "eth2" ];
           networking.interfaces.eth1.ipv4.addresses = [
@@ -80,11 +81,13 @@ in
       start_all()
 
       # Wait for network and miniupnpd.
+      router.systemctl("start network-online.target")
       router.wait_for_unit("network-online.target")
       # $router.wait_for_unit("nat")
-      router.wait_for_unit("firewall.service")
+      router.wait_for_unit("${if useNftables then "nftables" else "firewall"}.service")
       router.wait_for_unit("miniupnpd")
 
+      client1.systemctl("start network-online.target")
       client1.wait_for_unit("network-online.target")
 
       client1.succeed("upnpc -a ${internalClient1Address} 9000 9000 TCP")
diff --git a/nixos/tests/uptermd.nix b/nixos/tests/uptermd.nix
index 429e3c9dd5ff3..469aa5047c27c 100644
--- a/nixos/tests/uptermd.nix
+++ b/nixos/tests/uptermd.nix
@@ -28,6 +28,7 @@ in
     start_all()
 
     server.wait_for_unit("uptermd.service")
+    server.systemctl("start network-online.target")
     server.wait_for_unit("network-online.target")
 
     # wait for upterm port to be reachable
diff --git a/nixos/tests/varnish.nix b/nixos/tests/varnish.nix
index 9dcdeec9d8c8f..76cea1ada5477 100644
--- a/nixos/tests/varnish.nix
+++ b/nixos/tests/varnish.nix
@@ -3,12 +3,12 @@
 , pkgs ? import ../.. { inherit system; }
 , package
 }:
-import ./make-test-python.nix ({ pkgs, ... }: let
+import ./make-test-python.nix ({ pkgs, lib, ... }: let
   testPath = pkgs.hello;
 in {
   name = "varnish";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ ajs124 ];
+  meta = {
+    maintainers = lib.teams.helsinki-systems.members;
   };
 
   nodes = {
diff --git a/nixos/tests/vaultwarden.nix b/nixos/tests/vaultwarden.nix
index 95d00c1d8ec14..9d2f0e6ab060e 100644
--- a/nixos/tests/vaultwarden.nix
+++ b/nixos/tests/vaultwarden.nix
@@ -54,9 +54,8 @@ let
             services.postgresql = {
               enable = true;
               initialScript = pkgs.writeText "postgresql-init.sql" ''
-                CREATE DATABASE bitwarden;
                 CREATE USER bitwardenuser WITH PASSWORD '${dbPassword}';
-                GRANT ALL PRIVILEGES ON DATABASE bitwarden TO bitwardenuser;
+                CREATE DATABASE bitwarden WITH OWNER bitwardenuser;
               '';
             };
 
@@ -174,7 +173,7 @@ let
           )
 
       with subtest("use the web interface to sign up, log in, and save a password"):
-          server.succeed("PYTHONUNBUFFERED=1 test-runner | systemd-cat -t test-runner")
+          server.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner")
 
       with subtest("log in with the cli"):
           key = client.succeed(
diff --git a/nixos/tests/vikunja.nix b/nixos/tests/vikunja.nix
index 2660aa9767ca7..60fd5ce13854e 100644
--- a/nixos/tests/vikunja.nix
+++ b/nixos/tests/vikunja.nix
@@ -33,7 +33,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         ensureDatabases = [ "vikunja-api" ];
         ensureUsers = [
           { name = "vikunja-api";
-            ensurePermissions = { "DATABASE \"vikunja-api\"" = "ALL PRIVILEGES"; };
+            ensureDBOwnership = true;
           }
         ];
       };
diff --git a/nixos/tests/watchdogd.nix b/nixos/tests/watchdogd.nix
new file mode 100644
index 0000000000000..663e97cbae104
--- /dev/null
+++ b/nixos/tests/watchdogd.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "watchdogd";
+  meta.maintainers = with lib.maintainers; [ vifino ];
+
+  nodes.machine = { pkgs, ... }: {
+    virtualisation.qemu.options = [
+      "-device i6300esb" # virtual watchdog timer
+    ];
+    boot.kernelModules = [ "i6300esb" ];
+    services.watchdogd.enable = true;
+    services.watchdogd.settings = {
+      supervisor.enabled = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("watchdogd.service")
+
+    assert "i6300ESB" in machine.succeed("watchdogctl status")
+    machine.succeed("watchdogctl test")
+  '';
+})
diff --git a/nixos/tests/web-apps/mastodon/remote-postgresql.nix b/nixos/tests/web-apps/mastodon/remote-postgresql.nix
index 715477191bfbc..6548883db4526 100644
--- a/nixos/tests/web-apps/mastodon/remote-postgresql.nix
+++ b/nixos/tests/web-apps/mastodon/remote-postgresql.nix
@@ -16,7 +16,7 @@ in
   meta.maintainers = with pkgs.lib.maintainers; [ erictapen izorkin ];
 
   nodes = {
-    database = {
+    database = { config, ... }: {
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -24,11 +24,13 @@ in
           ];
         };
         extraHosts = hosts;
-        firewall.allowedTCPPorts = [ 5432 ];
+        firewall.allowedTCPPorts = [ config.services.postgresql.port ];
       };
 
       services.postgresql = {
         enable = true;
+        # TODO remove once https://github.com/NixOS/nixpkgs/pull/266270 is resolved.
+        package = pkgs.postgresql_14;
         enableTCPIP = true;
         authentication = ''
           hostnossl mastodon_local mastodon_test 192.168.2.201/32 md5
@@ -41,7 +43,7 @@ in
       };
     };
 
-    nginx = {
+    nginx = { nodes, ... }: {
       networking = {
         interfaces.eth1 = {
           ipv4.addresses = [
@@ -69,18 +71,14 @@ in
             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";
+            proxyPass = "http://192.168.2.201:${toString nodes.server.services.mastodon.webPort}";
             proxyWebsockets = true;
           };
         };
       };
     };
 
-    server = { pkgs, ... }: {
+    server = { config, pkgs, ... }: {
       virtualisation.memorySize = 2048;
 
       environment = {
@@ -98,7 +96,10 @@ in
           ];
         };
         extraHosts = hosts;
-        firewall.allowedTCPPorts = [ 55001 55002 ];
+        firewall.allowedTCPPorts = [
+          config.services.mastodon.webPort
+          config.services.mastodon.sidekiqPort
+        ];
       };
 
       services.mastodon = {
@@ -106,6 +107,7 @@ in
         configureNginx = false;
         localDomain = "mastodon.local";
         enableUnixSocket = false;
+        streamingProcesses = 2;
         database = {
           createLocally = false;
           host = "192.168.2.102";
diff --git a/nixos/tests/web-apps/mastodon/script.nix b/nixos/tests/web-apps/mastodon/script.nix
index a89b4b7480e99..afb7c0e0a0ebe 100644
--- a/nixos/tests/web-apps/mastodon/script.nix
+++ b/nixos/tests/web-apps/mastodon/script.nix
@@ -10,9 +10,8 @@
 
   server.wait_for_unit("redis-mastodon.service")
   server.wait_for_unit("mastodon-sidekiq-all.service")
-  server.wait_for_unit("mastodon-streaming.service")
+  server.wait_for_unit("mastodon-streaming.target")
   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
diff --git a/nixos/tests/web-apps/mastodon/standard.nix b/nixos/tests/web-apps/mastodon/standard.nix
index 14311afea3f78..e5eb30fef5976 100644
--- a/nixos/tests/web-apps/mastodon/standard.nix
+++ b/nixos/tests/web-apps/mastodon/standard.nix
@@ -40,11 +40,15 @@ in
         port = 31637;
       };
 
+      # TODO remove once https://github.com/NixOS/nixpkgs/pull/266270 is resolved.
+      services.postgresql.package = pkgs.postgresql_14;
+
       services.mastodon = {
         enable = true;
         configureNginx = true;
         localDomain = "mastodon.local";
         enableUnixSocket = false;
+        streamingProcesses = 2;
         smtp = {
           createLocally = false;
           fromAddress = "mastodon@mastodon.local";
diff --git a/nixos/tests/web-apps/netbox-upgrade.nix b/nixos/tests/web-apps/netbox-upgrade.nix
index 602cf8d889d4f..4c554e7ae613b 100644
--- a/nixos/tests/web-apps/netbox-upgrade.nix
+++ b/nixos/tests/web-apps/netbox-upgrade.nix
@@ -1,13 +1,15 @@
 import ../make-test-python.nix ({ lib, pkgs, ... }: let
-  oldNetbox = pkgs.netbox_3_3;
+  oldNetbox = pkgs.netbox_3_6;
+  newNetbox = pkgs.netbox_3_7;
 in {
   name = "netbox-upgrade";
 
   meta = with lib.maintainers; {
-    maintainers = [ minijackson ];
+    maintainers = [ minijackson raitobezarius ];
   };
 
   nodes.machine = { config, ... }: {
+    virtualisation.memorySize = 2048;
     services.netbox = {
       enable = true;
       package = oldNetbox;
@@ -32,7 +34,7 @@ in {
 
     networking.firewall.allowedTCPPorts = [ 80 ];
 
-    specialisation.upgrade.configuration.services.netbox.package = lib.mkForce pkgs.netbox;
+    specialisation.upgrade.configuration.services.netbox.package = lib.mkForce newNetbox;
   };
 
   testScript = { nodes, ... }:
@@ -43,7 +45,7 @@ in {
         (lib.concatStringsSep ".")
       ];
       oldApiVersion = apiVersion oldNetbox.version;
-      newApiVersion = apiVersion pkgs.netbox.version;
+      newApiVersion = apiVersion newNetbox.version;
     in
     ''
       start_all()
diff --git a/nixos/tests/web-apps/netbox.nix b/nixos/tests/web-apps/netbox.nix
index 30de74f1886c0..233f16a8fe0de 100644
--- a/nixos/tests/web-apps/netbox.nix
+++ b/nixos/tests/web-apps/netbox.nix
@@ -16,6 +16,7 @@ in import ../make-test-python.nix ({ lib, pkgs, netbox, ... }: {
   };
 
   nodes.machine = { config, ... }: {
+    virtualisation.memorySize = 2048;
     services.netbox = {
       enable = true;
       package = netbox;
diff --git a/nixos/tests/web-servers/stargazer.nix b/nixos/tests/web-servers/stargazer.nix
index c522cfee5dbc0..f56d1b8c94545 100644
--- a/nixos/tests/web-servers/stargazer.nix
+++ b/nixos/tests/web-servers/stargazer.nix
@@ -1,4 +1,41 @@
 { pkgs, lib, ... }:
+let
+  test_script = pkgs.stdenv.mkDerivation rec {
+    pname = "stargazer-test-script";
+    inherit (pkgs.stargazer) version src;
+    buildInputs = with pkgs; [ (python3.withPackages (ps: with ps; [ cryptography ])) ];
+    dontBuild = true;
+    doCheck = false;
+    installPhase = ''
+      mkdir -p $out/bin
+      cp scripts/gemini-diagnostics $out/bin/test
+    '';
+  };
+  test_env = pkgs.stdenv.mkDerivation rec {
+    pname = "stargazer-test-env";
+    inherit (pkgs.stargazer) version src;
+    buildPhase = ''
+      cc test_data/cgi-bin/loop.c -o test_data/cgi-bin/loop
+    '';
+    doCheck = false;
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+    '';
+  };
+  scgi_server = pkgs.stdenv.mkDerivation rec {
+    pname = "stargazer-test-scgi-server";
+    inherit (pkgs.stargazer) version src;
+    buildInputs = with pkgs; [ python3 ];
+    dontConfigure = true;
+    dontBuild = true;
+    doCheck = false;
+    installPhase = ''
+      mkdir -p $out/bin
+      cp scripts/scgi-server $out/bin/scgi-server
+    '';
+  };
+in
 {
   name = "stargazer";
   meta = with lib.maintainers; { maintainers = [ gaykitty ]; };
@@ -7,25 +44,84 @@
     geminiserver = { pkgs, ... }: {
       services.stargazer = {
         enable = true;
+        connectionLogging = false;
+        requestTimeout = 1;
         routes = [
           {
             route = "localhost";
-            root = toString (pkgs.writeTextDir "index.gmi" ''
-              # Hello NixOS!
-            '');
+            root = "${test_env}/test_data/test_site";
+          }
+          {
+            route = "localhost=/en.gmi";
+            root = "${test_env}/test_data/test_site";
+            lang = "en";
+            charset = "ascii";
+          }
+          {
+            route = "localhost~/(.*).gemini";
+            root = "${test_env}/test_data/test_site";
+            rewrite = "\\1.gmi";
+            lang = "en";
+            charset = "ascii";
+          }
+          {
+            route = "localhost=/plain.txt";
+            root = "${test_env}/test_data/test_site";
+            lang = "en";
+            charset = "ascii";
+            cert-path = "/var/lib/gemini/certs/localhost.crt";
+            key-path = "/var/lib/gemini/certs/localhost.key";
+          }
+          {
+            route = "localhost:/cgi-bin";
+            root = "${test_env}/test_data";
+            cgi = true;
+            cgi-timeout = 5;
+          }
+          {
+            route = "localhost:/scgi";
+            scgi = true;
+            scgi-address = "127.0.0.1:1099";
+          }
+          {
+            route = "localhost=/root";
+            redirect = "..";
+            permanent = true;
+          }
+          {
+            route = "localhost=/priv.gmi";
+            root = "${test_env}/test_data/test_site";
+            client-cert = "${test_env}/test_data/client_cert/good.crt";
+          }
+          {
+            route = "example.com~(.*)";
+            redirect = "gemini://localhost";
+            rewrite = "\\1";
+          }
+          {
+            route = "localhost:/no-exist";
+            root = "./does_not_exist";
           }
         ];
       };
+      systemd.services.scgi_server = {
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${scgi_server}/bin/scgi-server";
+        };
+      };
     };
   };
 
   testScript = { nodes, ... }: ''
+    geminiserver.wait_for_unit("scgi_server")
+    geminiserver.wait_for_open_port(1099)
     geminiserver.wait_for_unit("stargazer")
     geminiserver.wait_for_open_port(1965)
 
-    with subtest("check is serving over gemini"):
-      response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965")
+    with subtest("stargazer test suite"):
+      response = geminiserver.succeed("sh -c 'cd ${test_env}; ${test_script}/bin/test'")
       print(response)
-      assert "Hello NixOS!" in response
   '';
 }
diff --git a/nixos/tests/web-servers/ttyd.nix b/nixos/tests/web-servers/ttyd.nix
new file mode 100644
index 0000000000000..d161673684b31
--- /dev/null
+++ b/nixos/tests/web-servers/ttyd.nix
@@ -0,0 +1,19 @@
+import ../make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "ttyd";
+  meta.maintainers = with lib.maintainers; [ stunkymonkey ];
+
+  nodes.machine = { pkgs, ... }: {
+    services.ttyd = {
+      enable = true;
+      username = "foo";
+      passwordFile = pkgs.writeText "password" "bar";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("ttyd.service")
+    machine.wait_for_open_port(7681)
+    response = machine.succeed("curl -vvv -u foo:bar -s -H 'Host: ttyd' http://127.0.0.1:7681/")
+    assert '<title>ttyd - Terminal</title>' in response, "Page didn't load successfully"
+  '';
+})
diff --git a/nixos/tests/wiki-js.nix b/nixos/tests/wiki-js.nix
index fd054a9c59093..8b3c51935a6c4 100644
--- a/nixos/tests/wiki-js.nix
+++ b/nixos/tests/wiki-js.nix
@@ -10,14 +10,15 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
       enable = true;
       settings.db.host = "/run/postgresql";
       settings.db.user = "wiki-js";
+      settings.db.db = "wiki-js";
       settings.logLevel = "debug";
     };
     services.postgresql = {
       enable = true;
-      ensureDatabases = [ "wiki" ];
+      ensureDatabases = [ "wiki-js" ];
       ensureUsers = [
         { name = "wiki-js";
-          ensurePermissions."DATABASE wiki" = "ALL PRIVILEGES";
+          ensureDBOwnership = true;
         }
       ];
     };
diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix
index 4e322774fef59..592af9a094f1d 100644
--- a/nixos/tests/wordpress.nix
+++ b/nixos/tests/wordpress.nix
@@ -67,7 +67,7 @@ rec {
       networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
     };
   }) {} [
-    "6_1" "6_2"
+    "6_3" "6_4"
   ];
 
   testScript = ''
diff --git a/nixos/tests/wrappers.nix b/nixos/tests/wrappers.nix
index 391e9b42b45bd..1d4fa85d73993 100644
--- a/nixos/tests/wrappers.nix
+++ b/nixos/tests/wrappers.nix
@@ -21,6 +21,8 @@ in
       };
     };
 
+    security.apparmor.enable = true;
+
     security.wrappers = {
       suidRoot = {
         owner = "root";
@@ -84,17 +86,27 @@ in
       test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0')
       test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0')
 
+      # Test that in nonewprivs environment the wrappers simply exec their target.
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -u', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -g', '${toString usersGid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/suid_root_busybox id -rg', '${toString usersGid}')
+
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -u', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -ru', '${toString userUid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /run/wrappers/bin/sgid_root_busybox id -g', '${toString usersGid}')
+      test_as_regular('${pkgs.util-linux}/bin/setpriv --no-new-privs /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'))
 
-      # test a few "attacks" against which the wrapper protects itself
-      machine.succeed("cp /run/wrappers/bin/suid_root_busybox{,.real} /tmp/")
-      machine.fail(cmd_as_regular("/tmp/suid_root_busybox id -u"))
-
-      machine.succeed("chmod u+s,a+w /run/wrappers/bin/suid_root_busybox")
-      machine.fail(cmd_as_regular("/run/wrappers/bin/suid_root_busybox id -u"))
+      # Test that the only user of apparmor policy includes generated by
+      # wrappers works. Ideally this'd be located in a test for the module that
+      # actually makes the apparmor policy for ping, but there's no convenient
+      # test for that one.
+      machine.succeed("ping -c 1 127.0.0.1")
     '';
 })
diff --git a/nixos/tests/xfce.nix b/nixos/tests/xfce.nix
index 3758ccbccf425..9620e9188cbf5 100644
--- a/nixos/tests/xfce.nix
+++ b/nixos/tests/xfce.nix
@@ -20,26 +20,56 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       };
 
       services.xserver.desktopManager.xfce.enable = true;
+      environment.systemPackages = [ pkgs.xfce.xfce4-whiskermenu-plugin ];
 
       hardware.pulseaudio.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
 
     };
 
+  enableOCR = true;
+
   testScript = { nodes, ... }: let
     user = nodes.machine.users.users.alice;
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus";
   in ''
-      machine.wait_for_x()
-      machine.wait_for_file("${user.home}/.Xauthority")
-      machine.succeed("xauth merge ${user.home}/.Xauthority")
-      machine.wait_for_window("xfce4-panel")
-      machine.sleep(10)
-
-      # Check that logging in has given the user ownership of devices.
-      machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
-
-      machine.succeed("su - ${user.name} -c 'DISPLAY=:0.0 xfce4-terminal >&2 &'")
-      machine.wait_for_window("Terminal")
-      machine.sleep(10)
-      machine.screenshot("screen")
+      with subtest("Wait for login"):
+        machine.wait_for_x()
+        machine.wait_for_file("${user.home}/.Xauthority")
+        machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+        machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if Xfce components actually start"):
+        machine.wait_for_window("xfce4-panel")
+        machine.wait_for_window("Desktop")
+        for i in ["xfwm4", "xfsettingsd", "xfdesktop", "xfce4-screensaver", "xfce4-notifyd", "xfconfd"]:
+          machine.wait_until_succeeds(f"pgrep -f {i}")
+
+      with subtest("Open whiskermenu"):
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfconf-query -c xfce4-panel -p /plugins/plugin-1 -t string -s whiskermenu -n >&2 &'")
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfconf-query -c xfce4-panel -p /plugins/plugin-1/stay-on-focus-out -t bool -s true -n >&2 &'")
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfce4-panel -r >&2 &'")
+        machine.wait_until_succeeds("journalctl -b --grep 'xfce4-panel: Restarting' -t xsession")
+        machine.sleep(5)
+        machine.wait_until_succeeds("pgrep -f libwhiskermenu")
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfce4-popup-whiskermenu >&2 &'")
+        machine.wait_for_text('Mail Reader')
+        # Close the menu.
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 ${bus} xfce4-popup-whiskermenu >&2 &'")
+
+      with subtest("Open Xfce terminal"):
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 xfce4-terminal >&2 &'")
+        machine.wait_for_window("Terminal")
+
+      with subtest("Open Thunar"):
+        machine.succeed("su - ${user.name} -c 'DISPLAY=:0 thunar >&2 &'")
+        machine.wait_for_window("Thunar")
+        machine.wait_for_text('(Pictures|Public|Templates|Videos)')
+
+      with subtest("Check if any coredumps are found"):
+        machine.succeed("(coredumpctl --json=short 2>&1 || true) | grep 'No coredumps found'")
+        machine.sleep(10)
+        machine.screenshot("screen")
     '';
 })
diff --git a/nixos/tests/xmpp/ejabberd.nix b/nixos/tests/xmpp/ejabberd.nix
index 7926fe80de2fd..1a807b27b6f65 100644
--- a/nixos/tests/xmpp/ejabberd.nix
+++ b/nixos/tests/xmpp/ejabberd.nix
@@ -1,7 +1,7 @@
 import ../make-test-python.nix ({ pkgs, ... }: {
   name = "ejabberd";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ ajs124 ];
+    maintainers = [ ];
   };
   nodes = {
     client = { nodes, pkgs, ... }: {
diff --git a/nixos/tests/xrdp-with-audio-pulseaudio.nix b/nixos/tests/xrdp-with-audio-pulseaudio.nix
new file mode 100644
index 0000000000000..27da7c457c493
--- /dev/null
+++ b/nixos/tests/xrdp-with-audio-pulseaudio.nix
@@ -0,0 +1,97 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  # How to interactively test this module if the audio actually works
+
+  # - nix run .#pulseaudio-module-xrdp.tests.xrdp-with-audio-pulseaudio.driverInteractive
+  # - test_script() # launches the terminal and the tests itself
+  # - server.send_monitor_command("hostfwd_add tcp::3389-:3389") # forward the RDP port to the host
+  # - Connect with the RDP client you like (ex: Remmina)
+  # - Don't forget to enable audio support. In remmina: Advanced -> Audio output mode to Local (default is Off)
+  # - Open a browser or something that plays sound. Ex: chromium
+
+  name = "xrdp-with-audio-pulseaudio";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ lucasew ];
+  };
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ ./common/user-account.nix ];
+
+      environment.etc."xrdp/test.txt".text = "Shouldn't conflict";
+
+      services.xrdp.enable = true;
+      services.xrdp.audio.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.xterm}/bin/xterm";
+
+      hardware.pulseaudio = {
+        enable = true;
+      };
+
+      systemd.user.services.pactl-list = {
+        script = ''
+          while [ ! -S /tmp/.xrdp/xrdp_chansrv_audio_in_socket_* ]; do
+            sleep 1
+          done
+          sleep 1
+          ${pkgs.pulseaudio}/bin/pactl list
+          echo Source:
+          ${pkgs.pulseaudio}/bin/pactl get-default-source | tee /tmp/pulseaudio-source
+          echo Sink:
+          ${pkgs.pulseaudio}/bin/pactl get-default-sink | tee /tmp/pulseaudio-sink
+
+        '';
+        wantedBy = [ "default.target" ];
+      };
+
+      networking.firewall.allowedTCPPorts = [ 3389 ];
+    };
+
+    client = { pkgs, ... }: {
+      imports = [ ./common/x11.nix ./common/user-account.nix ];
+      test-support.displayManager.auto.user = "alice";
+
+      environment.systemPackages = [ pkgs.freerdp ];
+
+      services.xrdp.enable = true;
+      services.xrdp.audio.enable = true;
+      services.xrdp.defaultWindowManager = "${pkgs.icewm}/bin/icewm";
+
+      hardware.pulseaudio = {
+        enable = true;
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: let
+    user = nodes.client.config.users.users.alice;
+  in ''
+    start_all()
+
+    client.wait_for_x()
+    client.wait_for_file("${user.home}/.Xauthority")
+    client.succeed("xauth merge ${user.home}/.Xauthority")
+
+    client.sleep(5)
+
+    client.execute("xterm >&2 &")
+    client.sleep(1)
+
+    client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:127.0.0.1 /u:${user.name} /p:${user.password} /sound\n")
+
+    client.sleep(10)
+
+    client.succeed("[ -S /tmp/.xrdp/xrdp_chansrv_audio_in_socket_* ]") # checks if it's a socket
+    client.sleep(5)
+    client.screenshot("localrdp")
+
+    client.execute("xterm >&2 &")
+    client.sleep(1)
+    client.send_chars("xfreerdp /cert-tofu /w:640 /h:480 /v:server /u:${user.name} /p:${user.password} /sound\n")
+    client.sleep(10)
+
+    server.succeed("[ -S /tmp/.xrdp/xrdp_chansrv_audio_in_socket_* ]") # checks if it's a socket
+    server.succeed('[ "$(cat /tmp/pulseaudio-source)" == "xrdp-source" ]')
+    server.succeed('[ "$(cat /tmp/pulseaudio-sink)" == "xrdp-sink" ]')
+    client.screenshot("remoterdp")
+  '';
+})
diff --git a/nixos/tests/xscreensaver.nix b/nixos/tests/xscreensaver.nix
new file mode 100644
index 0000000000000..820ddbb0e9626
--- /dev/null
+++ b/nixos/tests/xscreensaver.nix
@@ -0,0 +1,64 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "pass-secret-service";
+  meta.maintainers = with lib.maintainers; [ vancluever AndersonTorres ];
+
+  nodes = {
+    ok = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+      };
+
+    empty_wrapperPrefix = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+        nixpkgs.overlays = [
+          (self: super: {
+            xscreensaver = super.xscreensaver.override {
+              wrapperPrefix = "";
+            };
+          })
+        ];
+      };
+
+    bad_wrapperPrefix = { nodes, pkgs, ... }:
+      {
+        imports = [ ./common/x11.nix ./common/user-account.nix ];
+        test-support.displayManager.auto.user = "alice";
+        services.xscreensaver.enable = true;
+        nixpkgs.overlays = [
+          (self: super: {
+            xscreensaver = super.xscreensaver.override {
+              wrapperPrefix = "/a/bad/path";
+            };
+          })
+        ];
+      };
+  };
+
+  testScript = ''
+    ok.wait_for_x()
+    ok.wait_for_unit("xscreensaver", "alice")
+    _, output_ok = ok.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' not in output_ok
+    assert 'your screen via the out-of-memory killer' not in output_ok
+    assert '"xscreensaver-auth" must be setuid root' not in output_ok
+
+    empty_wrapperPrefix.wait_for_x()
+    empty_wrapperPrefix.wait_for_unit("xscreensaver", "alice")
+    _, output_empty_wrapperPrefix = empty_wrapperPrefix.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' in output_empty_wrapperPrefix
+    assert 'your screen via the out-of-memory killer' in output_empty_wrapperPrefix
+    assert '"xscreensaver-auth" must be setuid root' in output_empty_wrapperPrefix
+
+    bad_wrapperPrefix.wait_for_x()
+    bad_wrapperPrefix.wait_for_unit("xscreensaver", "alice")
+    _, output_bad_wrapperPrefix = bad_wrapperPrefix.systemctl("status xscreensaver", "alice")
+    assert 'To prevent the kernel from randomly unlocking' in output_bad_wrapperPrefix
+    assert 'your screen via the out-of-memory killer' in output_bad_wrapperPrefix
+    assert '"xscreensaver-auth" must be setuid root' in output_bad_wrapperPrefix
+  '';
+})
diff --git a/nixos/tests/yggdrasil.nix b/nixos/tests/yggdrasil.nix
index eaf14e29acb03..70d148380bf75 100644
--- a/nixos/tests/yggdrasil.nix
+++ b/nixos/tests/yggdrasil.nix
@@ -116,6 +116,7 @@ in import ./make-test-python.nix ({ pkgs, ...} : {
         networking.firewall.allowedTCPPorts = [ 43210 ];
         services.yggdrasil = {
           enable = true;
+          extraArgs = [ "-loglevel" "error" ];
           denyDhcpcdInterfaces = [ "ygg0" ];
           settings = {
             IfTAPMode = true;
diff --git a/nixos/tests/zammad.nix b/nixos/tests/zammad.nix
index 7a2d40e82b3ed..faae1949e37b9 100644
--- a/nixos/tests/zammad.nix
+++ b/nixos/tests/zammad.nix
@@ -4,9 +4,13 @@ import ./make-test-python.nix (
   {
     name = "zammad";
 
-    meta.maintainers = with lib.maintainers; [ garbas taeer n0emis ];
+    meta.maintainers = with lib.maintainers; [ taeer n0emis netali ];
 
     nodes.machine = { config, ... }: {
+      virtualisation = {
+        memorySize = 2048;
+      };
+
       services.zammad.enable = true;
       services.zammad.secretKeyBaseFile = pkgs.writeText "secret" ''
         52882ef142066e09ab99ce816ba72522e789505caba224a52d750ec7dc872c2c371b2fd19f16b25dfbdd435a4dd46cb3df9f82eb63fafad715056bdfe25740d6
@@ -44,9 +48,10 @@ import ./make-test-python.nix (
     testScript = ''
       start_all()
       machine.wait_for_unit("postgresql.service")
+      machine.wait_for_unit("redis-zammad.service")
       machine.wait_for_unit("zammad-web.service")
       machine.wait_for_unit("zammad-websocket.service")
-      machine.wait_for_unit("zammad-scheduler.service")
+      machine.wait_for_unit("zammad-worker.service")
       # wait for zammad to fully come up
       machine.sleep(120)
 
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index 8e52e00657456..8fedcf095af69 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -13,15 +13,16 @@ let
                       else pkgs.linuxPackages
     , enableUnstable ? false
     , enableSystemdStage1 ? false
+    , zfsPackage ? if enableUnstable then pkgs.zfs else pkgs.zfsUnstable
     , extraTest ? ""
     }:
     makeTest {
       name = "zfs-" + name;
       meta = with pkgs.lib.maintainers; {
-        maintainers = [ adisbladis elvishjerricco ];
+        maintainers = [ elvishjerricco ];
       };
 
-      nodes.machine = { pkgs, lib, ... }:
+      nodes.machine = { config, pkgs, lib, ... }:
         let
           usersharePath = "/var/lib/samba/usershares";
         in {
@@ -35,8 +36,8 @@ let
         boot.loader.efi.canTouchEfiVariables = true;
         networking.hostId = "deadbeef";
         boot.kernelPackages = kernelPackage;
+        boot.zfs.package = zfsPackage;
         boot.supportedFilesystems = [ "zfs" ];
-        boot.zfs.enableUnstable = enableUnstable;
         boot.initrd.systemd.enable = enableSystemdStage1;
 
         environment.systemPackages = [ pkgs.parted ];
@@ -133,9 +134,8 @@ let
             )
             machine.crash()
             machine.wait_for_unit("multi-user.target")
+            machine.succeed("zfs set sharesmb=on rpool/shared_smb")
             machine.succeed(
-                "zfs set sharesmb=on rpool/shared_smb",
-                "zfs share rpool/shared_smb",
                 "smbclient -gNL localhost | grep rpool_shared_smb",
                 "umount /tmp/mnt",
                 "zpool destroy rpool",
@@ -194,6 +194,11 @@ let
 
 in {
 
+  # maintainer: @raitobezarius
+  series_2_1 = makeZfsTest "2.1-series" {
+    zfsPackage = pkgs.zfs_2_1;
+  };
+
   stable = makeZfsTest "stable" { };
 
   unstable = makeZfsTest "unstable" {
@@ -205,6 +210,7 @@ in {
     enableSystemdStage1 = true;
   };
 
+  installerBoot = (import ./installer.nix { }).separateBootZfs;
   installer = (import ./installer.nix { }).zfsroot;
 
   expand-partitions = makeTest {
diff --git a/nixos/tests/zrepl.nix b/nixos/tests/zrepl.nix
index b16c7eddc7aec..bdf11122c73f6 100644
--- a/nixos/tests/zrepl.nix
+++ b/nixos/tests/zrepl.nix
@@ -42,6 +42,7 @@ import ./make-test-python.nix (
       start_all()
 
       with subtest("Wait for zrepl and network ready"):
+          host.systemctl("start network-online.target")
           host.wait_for_unit("network-online.target")
           host.wait_for_unit("zrepl.service")
 
diff --git a/nixos/tests/zwave-js.nix b/nixos/tests/zwave-js.nix
new file mode 100644
index 0000000000000..9239e6964fd78
--- /dev/null
+++ b/nixos/tests/zwave-js.nix
@@ -0,0 +1,31 @@
+import ./make-test-python.nix ({ pkgs, lib, ...} :
+
+let
+  secretsConfigFile = pkgs.writeText "secrets.json" (builtins.toJSON {
+    securityKeys = {
+      "S0_Legacy" = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+    };
+  });
+in {
+  name = "zwave-js";
+  meta.maintainers = with lib.maintainers; [ graham33 ];
+
+  nodes = {
+    machine = { config, ... }: {
+      services.zwave-js = {
+        enable = true;
+        serialPort = "/dev/null";
+        extraFlags = ["--mock-driver"];
+        inherit secretsConfigFile;
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    machine.wait_for_unit("zwave-js.service")
+    machine.wait_for_open_port(3000)
+    machine.wait_until_succeeds("journalctl --since -1m --unit zwave-js --grep 'ZwaveJS server listening'")
+  '';
+})