about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/development/developing-the-test-driver.chapter.md2
-rw-r--r--nixos/doc/manual/development/freeform-modules.section.md2
-rw-r--r--nixos/doc/manual/development/option-declarations.section.md10
-rw-r--r--nixos/doc/manual/development/option-types.section.md22
-rw-r--r--nixos/doc/manual/development/settings-options.section.md4
-rw-r--r--nixos/doc/manual/development/writing-modules.chapter.md6
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.section.md5
-rw-r--r--nixos/doc/manual/installation/installing.chapter.md8
-rw-r--r--nixos/doc/manual/preface.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2305.section.md134
-rw-r--r--nixos/lib/eval-cacheable-options.nix1
-rw-r--r--nixos/lib/eval-config-minimal.nix1
-rw-r--r--nixos/lib/eval-config.nix24
-rw-r--r--nixos/lib/make-disk-image.nix2
-rw-r--r--nixos/lib/make-multi-disk-zfs-image.nix4
-rw-r--r--nixos/lib/make-single-disk-zfs-image.nix2
-rw-r--r--nixos/lib/test-driver/test_driver/driver.py7
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py26
-rw-r--r--nixos/lib/testing/default.nix5
-rw-r--r--nixos/lib/testing/driver.nix31
-rw-r--r--nixos/lib/testing/nodes.nix57
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image.nix6
-rw-r--r--nixos/maintainers/scripts/openstack/openstack-image-zfs.nix2
-rw-r--r--nixos/modules/config/gnu.nix1
-rw-r--r--nixos/modules/config/malloc.nix2
-rw-r--r--nixos/modules/config/no-x-libs.nix3
-rw-r--r--nixos/modules/config/swap.nix39
-rw-r--r--nixos/modules/config/users-groups.nix92
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix5
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix9
-rw-r--r--nixos/modules/installer/netboot/netboot.nix15
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl10
-rw-r--r--nixos/modules/installer/tools/tools.nix10
-rw-r--r--nixos/modules/misc/documentation.nix1
-rw-r--r--nixos/modules/misc/nixpkgs.nix6
-rw-r--r--nixos/modules/misc/nixpkgs/read-only.nix74
-rw-r--r--nixos/modules/misc/nixpkgs/test.nix59
-rw-r--r--nixos/modules/module-list.nix26
-rw-r--r--nixos/modules/programs/darling.nix21
-rw-r--r--nixos/modules/programs/firefox.nix4
-rw-r--r--nixos/modules/programs/fzf.nix19
-rw-r--r--nixos/modules/programs/gamescope.nix85
-rw-r--r--nixos/modules/programs/hyprland.nix7
-rw-r--r--nixos/modules/programs/less.nix2
-rw-r--r--nixos/modules/programs/miriway.nix20
-rw-r--r--nixos/modules/programs/neovim.nix52
-rw-r--r--nixos/modules/programs/shadow.nix2
-rw-r--r--nixos/modules/programs/sniffnet.nix24
-rw-r--r--nixos/modules/programs/steam.nix64
-rw-r--r--nixos/modules/programs/tmux.nix21
-rw-r--r--nixos/modules/programs/trippy.nix24
-rw-r--r--nixos/modules/security/acme/default.nix10
-rw-r--r--nixos/modules/security/apparmor/includes.nix2
-rw-r--r--nixos/modules/security/pam.nix63
-rw-r--r--nixos/modules/security/tpm2.nix2
-rw-r--r--nixos/modules/security/wrappers/default.nix2
-rw-r--r--nixos/modules/services/audio/gonic.nix89
-rw-r--r--nixos/modules/services/audio/navidrome.nix4
-rw-r--r--nixos/modules/services/audio/snapserver.nix4
-rw-r--r--nixos/modules/services/backup/automysqlbackup.nix21
-rw-r--r--nixos/modules/services/backup/borgbackup.nix2
-rw-r--r--nixos/modules/services/backup/mysql-backup.nix2
-rw-r--r--nixos/modules/services/backup/restic.nix5
-rw-r--r--nixos/modules/services/blockchain/ethereum/geth.nix4
-rw-r--r--nixos/modules/services/continuous-integration/gitea-actions-runner.nix237
-rw-r--r--nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix3
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/job-builder.nix2
-rw-r--r--nixos/modules/services/databases/lldap.nix121
-rw-r--r--nixos/modules/services/databases/postgresql.nix2
-rw-r--r--nixos/modules/services/desktops/deepin/app-services.nix36
-rw-r--r--nixos/modules/services/desktops/deepin/dde-api.nix50
-rw-r--r--nixos/modules/services/desktops/deepin/dde-daemon.nix40
-rw-r--r--nixos/modules/services/desktops/system76-scheduler.nix296
-rw-r--r--nixos/modules/services/development/lorri.nix2
-rw-r--r--nixos/modules/services/editors/emacs.md37
-rw-r--r--nixos/modules/services/games/asf.nix2
-rw-r--r--nixos/modules/services/games/minetest-server.nix2
-rw-r--r--nixos/modules/services/hardware/asusd.nix64
-rw-r--r--nixos/modules/services/hardware/auto-cpufreq.nix2
-rw-r--r--nixos/modules/services/hardware/keyd.nix18
-rw-r--r--nixos/modules/services/hardware/udev.nix13
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix1
-rw-r--r--nixos/modules/services/logging/graylog.nix4
-rw-r--r--nixos/modules/services/logging/logrotate.nix5
-rw-r--r--nixos/modules/services/logging/syslogd.nix2
-rw-r--r--nixos/modules/services/logging/vector.nix15
-rw-r--r--nixos/modules/services/mail/maddy.nix126
-rw-r--r--nixos/modules/services/mail/postfix.nix2
-rw-r--r--nixos/modules/services/mail/roundcube.nix16
-rw-r--r--nixos/modules/services/matrix/mjolnir.nix4
-rw-r--r--nixos/modules/services/misc/etcd.nix3
-rw-r--r--nixos/modules/services/misc/fstrim.nix2
-rw-r--r--nixos/modules/services/misc/gammu-smsd.nix10
-rw-r--r--nixos/modules/services/misc/gitea.nix234
-rw-r--r--nixos/modules/services/misc/gitlab.nix2
-rw-r--r--nixos/modules/services/misc/klipper.nix16
-rw-r--r--nixos/modules/services/misc/mbpfan.nix2
-rw-r--r--nixos/modules/services/misc/moonraker.nix2
-rw-r--r--nixos/modules/services/misc/pufferpanel.nix176
-rw-r--r--nixos/modules/services/misc/redmine.nix14
-rw-r--r--nixos/modules/services/misc/rshim.nix99
-rw-r--r--nixos/modules/services/misc/siproxd.nix2
-rw-r--r--nixos/modules/services/misc/snapper.nix146
-rw-r--r--nixos/modules/services/misc/tandoor-recipes.nix1
-rw-r--r--nixos/modules/services/monitoring/datadog-agent.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana-agent.nix24
-rw-r--r--nixos/modules/services/monitoring/grafana.nix12
-rw-r--r--nixos/modules/services/monitoring/netdata.nix16
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix24
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.md2
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/collectd.nix4
-rw-r--r--nixos/modules/services/monitoring/uptime-kuma.nix2
-rw-r--r--nixos/modules/services/network-filesystems/openafs/lib.nix4
-rw-r--r--nixos/modules/services/network-filesystems/webdav-server-rs.nix8
-rw-r--r--nixos/modules/services/networking/alice-lg.nix101
-rw-r--r--nixos/modules/services/networking/bird-lg.nix112
-rw-r--r--nixos/modules/services/networking/birdwatcher.nix129
-rw-r--r--nixos/modules/services/networking/consul.nix8
-rw-r--r--nixos/modules/services/networking/harmonia.nix88
-rw-r--r--nixos/modules/services/networking/iscsi/root-initiator.nix4
-rw-r--r--nixos/modules/services/networking/ivpn.nix51
-rw-r--r--nixos/modules/services/networking/ndppd.nix2
-rw-r--r--nixos/modules/services/networking/netbird.nix5
-rw-r--r--nixos/modules/services/networking/ntopng.nix2
-rw-r--r--nixos/modules/services/networking/picosnitch.nix26
-rw-r--r--nixos/modules/services/networking/ssh/lshd.nix4
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix13
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix51
-rw-r--r--nixos/modules/services/networking/strongswan.nix6
-rw-r--r--nixos/modules/services/networking/stunnel.nix4
-rw-r--r--nixos/modules/services/networking/syncplay.nix44
-rw-r--r--nixos/modules/services/networking/syncthing.nix508
-rw-r--r--nixos/modules/services/networking/wgautomesh.nix161
-rw-r--r--nixos/modules/services/networking/wstunnel.nix4
-rw-r--r--nixos/modules/services/networking/xinetd.nix2
-rw-r--r--nixos/modules/services/printing/cupsd.nix1
-rw-r--r--nixos/modules/services/security/authelia.nix2
-rw-r--r--nixos/modules/services/security/fail2ban.nix99
-rw-r--r--nixos/modules/services/security/kanidm.nix102
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix5
-rw-r--r--nixos/modules/services/security/vault-agent.nix128
-rw-r--r--nixos/modules/services/system/cachix-agent/default.nix2
-rw-r--r--nixos/modules/services/system/cachix-watch-store.nix6
-rw-r--r--nixos/modules/services/system/cloud-init.nix283
-rw-r--r--nixos/modules/services/system/dbus.nix21
-rw-r--r--nixos/modules/services/torrent/deluge.nix2
-rw-r--r--nixos/modules/services/web-apps/discourse.nix7
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix126
-rw-r--r--nixos/modules/services/web-apps/kavita.nix83
-rw-r--r--nixos/modules/services/web-apps/lemmy.nix8
-rw-r--r--nixos/modules/services/web-apps/mainsail.nix66
-rw-r--r--nixos/modules/services/web-apps/matomo.md2
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix123
-rw-r--r--nixos/modules/services/web-apps/monica.nix468
-rw-r--r--nixos/modules/services/web-apps/moodle.nix2
-rw-r--r--nixos/modules/services/web-apps/nextcloud-notify_push.nix43
-rw-r--r--nixos/modules/services/web-apps/nextcloud.md40
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix98
-rw-r--r--nixos/modules/services/web-apps/peertube.nix8
-rw-r--r--nixos/modules/services/web-apps/pict-rs.nix4
-rw-r--r--nixos/modules/services/web-apps/pixelfed.nix478
-rw-r--r--nixos/modules/services/web-apps/plausible.nix14
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix2
-rw-r--r--nixos/modules/services/web-apps/vikunja.nix8
-rw-r--r--nixos/modules/services/web-apps/wiki-js.nix2
-rw-r--r--nixos/modules/services/web-servers/fcgiwrap.nix2
-rw-r--r--nixos/modules/services/web-servers/lighttpd/default.nix10
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix41
-rw-r--r--nixos/modules/services/web-servers/stargazer.nix226
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix6
-rw-r--r--nixos/modules/services/web-servers/unit/default.nix2
-rw-r--r--nixos/modules/services/web-servers/varnish/default.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/budgie.nix59
-rw-r--r--nixos/modules/services/x11/desktop-managers/deepin.nix208
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix3
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix4
-rw-r--r--nixos/modules/services/x11/xserver.nix4
-rw-r--r--nixos/modules/system/activation/bootspec.cue27
-rw-r--r--nixos/modules/system/activation/bootspec.nix48
-rw-r--r--nixos/modules/system/activation/top-level.nix35
-rw-r--r--nixos/modules/system/boot/binfmt.nix4
-rw-r--r--nixos/modules/system/boot/grow-partition.nix5
-rw-r--r--nixos/modules/system/boot/initrd-network.nix6
-rw-r--r--nixos/modules/system/boot/initrd-openvpn.nix21
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix64
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix109
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl474
-rw-r--r--nixos/modules/system/boot/loader/grub/ipxe.nix6
-rw-r--r--nixos/modules/system/boot/loader/grub/memtest.nix14
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix6
-rw-r--r--nixos/modules/system/boot/luksroot.nix3
-rw-r--r--nixos/modules/system/boot/networkd.nix197
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh8
-rw-r--r--nixos/modules/system/boot/stage-1.nix3
-rw-r--r--nixos/modules/system/boot/systemd/initrd-secrets.nix4
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix61
-rw-r--r--nixos/modules/system/boot/systemd/repart.nix39
-rw-r--r--nixos/modules/tasks/filesystems.nix2
-rw-r--r--nixos/modules/tasks/filesystems/envfs.nix3
-rw-r--r--nixos/modules/tasks/filesystems/erofs.nix21
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix302
-rw-r--r--nixos/modules/testing/test-instrumentation.nix12
-rw-r--r--nixos/modules/virtualisation/azure-common.nix1
-rw-r--r--nixos/modules/virtualisation/lxc-container.nix5
-rw-r--r--nixos/modules/virtualisation/multipass.nix4
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix12
-rw-r--r--nixos/modules/virtualisation/parallels-guest.nix1
-rw-r--r--nixos/modules/virtualisation/proxmox-image.nix16
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix349
-rw-r--r--nixos/modules/virtualisation/rosetta.nix10
-rw-r--r--nixos/modules/virtualisation/xen-domU.nix1
-rw-r--r--nixos/release.nix6
-rw-r--r--nixos/tests/aaaaxy.nix29
-rw-r--r--nixos/tests/alice-lg.nix44
-rw-r--r--nixos/tests/all-tests.nix57
-rw-r--r--nixos/tests/birdwatcher.nix94
-rw-r--r--nixos/tests/bootspec.nix14
-rw-r--r--nixos/tests/bpf.nix7
-rw-r--r--nixos/tests/budgie.nix7
-rw-r--r--nixos/tests/consul-template.nix36
-rw-r--r--nixos/tests/containers-imperative.nix4
-rw-r--r--nixos/tests/darling.nix44
-rw-r--r--nixos/tests/deepin.nix57
-rw-r--r--nixos/tests/early-mount-options.nix19
-rw-r--r--nixos/tests/ft2-clone.nix4
-rw-r--r--nixos/tests/geth.nix8
-rw-r--r--nixos/tests/gitea.nix3
-rw-r--r--nixos/tests/gitlab.nix17
-rw-r--r--nixos/tests/gonic.nix18
-rw-r--r--nixos/tests/harmonia.nix37
-rw-r--r--nixos/tests/headscale.nix2
-rw-r--r--nixos/tests/hibernate.nix2
-rw-r--r--nixos/tests/home-assistant.nix9
-rw-r--r--nixos/tests/initrd-luks-empty-passphrase.nix10
-rw-r--r--nixos/tests/initrd-network-openvpn/default.nix20
-rw-r--r--nixos/tests/initrd-network-ssh/default.nix4
-rw-r--r--nixos/tests/initrd-secrets-changing.nix1
-rw-r--r--nixos/tests/installed-tests/pipewire.nix12
-rw-r--r--nixos/tests/installer.nix52
-rw-r--r--nixos/tests/kanidm.nix4
-rw-r--r--nixos/tests/kavita.nix36
-rw-r--r--nixos/tests/lldap.nix26
-rw-r--r--nixos/tests/luks.nix8
-rw-r--r--nixos/tests/lvm2/systemd-stage-1.nix8
-rw-r--r--nixos/tests/maddy/default.nix6
-rw-r--r--nixos/tests/maddy/tls.nix94
-rw-r--r--nixos/tests/maddy/unencrypted.nix (renamed from nixos/tests/maddy.nix)4
-rw-r--r--nixos/tests/matrix/mjolnir.nix3
-rw-r--r--nixos/tests/mediawiki.nix24
-rw-r--r--nixos/tests/mosquitto.nix1
-rw-r--r--nixos/tests/mysql/mysql-replication.nix4
-rw-r--r--nixos/tests/mysql/mysql.nix2
-rw-r--r--nixos/tests/nextcloud/basic.nix1
-rw-r--r--nixos/tests/nextcloud/default.nix2
-rw-r--r--nixos/tests/nextcloud/openssl-sse.nix1
-rw-r--r--nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix43
-rw-r--r--nixos/tests/nextcloud/with-mysql-and-memcached.nix10
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix21
-rw-r--r--nixos/tests/nfs/simple.nix3
-rw-r--r--nixos/tests/nginx.nix18
-rw-r--r--nixos/tests/nixos-test-driver/extra-python-packages.nix (renamed from nixos/tests/extra-python-packages.nix)2
-rw-r--r--nixos/tests/nixos-test-driver/node-name.nix33
-rw-r--r--nixos/tests/non-default-filesystems.nix132
-rw-r--r--nixos/tests/nzbget.nix2
-rw-r--r--nixos/tests/pam/zfs-key.nix83
-rw-r--r--nixos/tests/pgadmin4.nix12
-rw-r--r--nixos/tests/power-profiles-daemon.nix1
-rw-r--r--nixos/tests/pppd.nix4
-rw-r--r--nixos/tests/predictable-interface-names.nix39
-rw-r--r--nixos/tests/prometheus-exporters.nix7
-rw-r--r--nixos/tests/promscale.nix60
-rw-r--r--nixos/tests/pufferpanel.nix74
-rw-r--r--nixos/tests/restic.nix36
-rw-r--r--nixos/tests/rshim.nix25
-rw-r--r--nixos/tests/sssd-ldap.nix80
-rw-r--r--nixos/tests/swap-file-btrfs.nix2
-rw-r--r--nixos/tests/swap-partition.nix2
-rw-r--r--nixos/tests/swap-random-encryption.nix80
-rw-r--r--nixos/tests/syncthing-init.nix16
-rw-r--r--nixos/tests/systemd-initrd-btrfs-raid.nix8
-rw-r--r--nixos/tests/systemd-initrd-luks-fido2.nix8
-rw-r--r--nixos/tests/systemd-initrd-luks-keyfile.nix6
-rw-r--r--nixos/tests/systemd-initrd-luks-password.nix12
-rw-r--r--nixos/tests/systemd-initrd-luks-tpm2.nix8
-rw-r--r--nixos/tests/systemd-initrd-networkd-ssh.nix82
-rw-r--r--nixos/tests/systemd-initrd-networkd.nix74
-rw-r--r--nixos/tests/systemd-initrd-simple.nix2
-rw-r--r--nixos/tests/systemd-initrd-swraid.nix6
-rw-r--r--nixos/tests/systemd-networkd-vrf.nix114
-rw-r--r--nixos/tests/systemd-repart.nix2
-rw-r--r--nixos/tests/vault-agent.nix52
-rw-r--r--nixos/tests/vector.nix2
-rw-r--r--nixos/tests/vikunja.nix5
-rw-r--r--nixos/tests/web-apps/monica.nix33
-rw-r--r--nixos/tests/web-apps/pixelfed/default.nix8
-rw-r--r--nixos/tests/web-apps/pixelfed/standard.nix38
-rw-r--r--nixos/tests/web-apps/snipe-it.nix101
-rw-r--r--nixos/tests/web-servers/stargazer.nix31
-rw-r--r--nixos/tests/wiki-js.nix2
-rw-r--r--nixos/tests/wordpress.nix2
-rw-r--r--nixos/tests/zfs.nix38
302 files changed, 9022 insertions, 2417 deletions
diff --git a/nixos/doc/manual/development/developing-the-test-driver.chapter.md b/nixos/doc/manual/development/developing-the-test-driver.chapter.md
index 4b70fe00af476..d64574fa62aaa 100644
--- a/nixos/doc/manual/development/developing-the-test-driver.chapter.md
+++ b/nixos/doc/manual/development/developing-the-test-driver.chapter.md
@@ -25,6 +25,8 @@ These include `pkgs.nixosTest`, `testing-python.nix` and `make-test-python.nix`.
 
 ## Testing changes to the test framework {#sec-test-the-test-framework}
 
+We currently have limited unit tests for the framework itself. You may run these with `nix-build -A nixosTests.nixos-test-driver`.
+
 When making significant changes to the test framework, we run the tests on Hydra, to avoid disrupting the larger NixOS project.
 
 For this, we use the `python-test-refactoring` branch in the `NixOS/nixpkgs` repository, and its [corresponding Hydra jobset](https://hydra.nixos.org/jobset/nixos/python-test-refactoring).
diff --git a/nixos/doc/manual/development/freeform-modules.section.md b/nixos/doc/manual/development/freeform-modules.section.md
index 514a06f97ee77..4f344dd804601 100644
--- a/nixos/doc/manual/development/freeform-modules.section.md
+++ b/nixos/doc/manual/development/freeform-modules.section.md
@@ -13,7 +13,7 @@ checking for entire option trees, it is only recommended for use in
 submodules.
 
 ::: {#ex-freeform-module .example}
-**Example: Freeform submodule**
+### Freeform submodule
 
 The following shows a submodule assigning a freeform type that allows
 arbitrary attributes with `str` values below `settings`, but also
diff --git a/nixos/doc/manual/development/option-declarations.section.md b/nixos/doc/manual/development/option-declarations.section.md
index f6fed3e16837f..3448b07722b85 100644
--- a/nixos/doc/manual/development/option-declarations.section.md
+++ b/nixos/doc/manual/development/option-declarations.section.md
@@ -77,6 +77,7 @@ The option's description is "Whether to enable \<name\>.".
 For example:
 
 ::: {#ex-options-declarations-util-mkEnableOption-magic .example}
+### `mkEnableOption` usage
 ```nix
 lib.mkEnableOption (lib.mdDoc "magic")
 # is like
@@ -126,6 +127,7 @@ During the transition to CommonMark documentation `mkPackageOption` creates an o
 Examples:
 
 ::: {#ex-options-declarations-util-mkPackageOption-hello .example}
+### Simple `mkPackageOption` usage
 ```nix
 lib.mkPackageOptionMD pkgs "hello" { }
 # is like
@@ -139,6 +141,7 @@ lib.mkOption {
 :::
 
 ::: {#ex-options-declarations-util-mkPackageOption-ghc .example}
+### `mkPackageOption` with explicit default and example
 ```nix
 lib.mkPackageOptionMD pkgs "GHC" {
   default = [ "ghc" ];
@@ -156,6 +159,7 @@ lib.mkOption {
 :::
 
 ::: {#ex-options-declarations-util-mkPackageOption-extraDescription .example}
+### `mkPackageOption` with additional description text
 ```nix
 mkPackageOption pkgs [ "python39Packages" "pytorch" ] {
   extraDescription = "This is an example and doesn't actually do anything.";
@@ -217,7 +221,7 @@ changing the main service module file and the type system automatically
 enforces that there can only be a single display manager enabled.
 
 ::: {#ex-option-declaration-eot-service .example}
-**Example: Extensible type placeholder in the service module**
+### Extensible type placeholder in the service module
 ```nix
 services.xserver.displayManager.enable = mkOption {
   description = "Display manager to use";
@@ -227,7 +231,7 @@ services.xserver.displayManager.enable = mkOption {
 :::
 
 ::: {#ex-option-declaration-eot-backend-gdm .example}
-**Example: Extending `services.xserver.displayManager.enable` in the `gdm` module**
+### Extending `services.xserver.displayManager.enable` in the `gdm` module
 ```nix
 services.xserver.displayManager.enable = mkOption {
   type = with types; nullOr (enum [ "gdm" ]);
@@ -236,7 +240,7 @@ services.xserver.displayManager.enable = mkOption {
 :::
 
 ::: {#ex-option-declaration-eot-backend-sddm .example}
-**Example: Extending `services.xserver.displayManager.enable` in the `sddm` module**
+### Extending `services.xserver.displayManager.enable` in the `sddm` module
 ```nix
 services.xserver.displayManager.enable = mkOption {
   type = with types; nullOr (enum [ "sddm" ]);
diff --git a/nixos/doc/manual/development/option-types.section.md b/nixos/doc/manual/development/option-types.section.md
index 51977c58333f9..9e156ebff9d3e 100644
--- a/nixos/doc/manual/development/option-types.section.md
+++ b/nixos/doc/manual/development/option-types.section.md
@@ -36,7 +36,7 @@ merging is handled.
     together. This type is recommended when the option type is unknown.
 
     ::: {#ex-types-anything .example}
-    **Example: `types.anything` Example**
+    ### `types.anything`
 
     Two definitions of this type like
 
@@ -99,6 +99,10 @@ merging is handled.
     problems.
     :::
 
+`types.pkgs`
+
+:   A type for the top level Nixpkgs package set.
+
 ### Numeric types {#sec-option-types-numeric}
 
 `types.int`
@@ -356,7 +360,7 @@ you will still need to provide a default value (e.g. an empty attribute set)
 if you want to allow users to leave it undefined.
 
 ::: {#ex-submodule-direct .example}
-**Example: Directly defined submodule**
+### Directly defined submodule
 ```nix
 options.mod = mkOption {
   description = "submodule example";
@@ -375,7 +379,7 @@ options.mod = mkOption {
 :::
 
 ::: {#ex-submodule-reference .example}
-**Example: Submodule defined as a reference**
+### Submodule defined as a reference
 ```nix
 let
   modOptions = {
@@ -403,7 +407,7 @@ multiple definitions of the submodule option set
 ([Example: Definition of a list of submodules](#ex-submodule-listof-definition)).
 
 ::: {#ex-submodule-listof-declaration .example}
-**Example: Declaration of a list of submodules**
+### Declaration of a list of submodules
 ```nix
 options.mod = mkOption {
   description = "submodule example";
@@ -422,7 +426,7 @@ options.mod = mkOption {
 :::
 
 ::: {#ex-submodule-listof-definition .example}
-**Example: Definition of a list of submodules**
+### Definition of a list of submodules
 ```nix
 config.mod = [
   { foo = 1; bar = "one"; }
@@ -437,7 +441,7 @@ multiple named definitions of the submodule option set
 ([Example: Definition of attribute sets of submodules](#ex-submodule-attrsof-definition)).
 
 ::: {#ex-submodule-attrsof-declaration .example}
-**Example: Declaration of attribute sets of submodules**
+### Declaration of attribute sets of submodules
 ```nix
 options.mod = mkOption {
   description = "submodule example";
@@ -456,7 +460,7 @@ options.mod = mkOption {
 :::
 
 ::: {#ex-submodule-attrsof-definition .example}
-**Example: Definition of attribute sets of submodules**
+### Definition of attribute sets of submodules
 ```nix
 config.mod.one = { foo = 1; bar = "one"; };
 config.mod.two = { foo = 2; bar = "two"; };
@@ -476,7 +480,7 @@ Types are mainly characterized by their `check` and `merge` functions.
     ([Example: Overriding a type check](#ex-extending-type-check-2)).
 
     ::: {#ex-extending-type-check-1 .example}
-    **Example: Adding a type check**
+    ### Adding a type check
 
     ```nix
     byte = mkOption {
@@ -487,7 +491,7 @@ Types are mainly characterized by their `check` and `merge` functions.
     :::
 
     ::: {#ex-extending-type-check-2 .example}
-    **Example: Overriding a type check**
+    ### Overriding a type check
 
     ```nix
     nixThings = mkOption {
diff --git a/nixos/doc/manual/development/settings-options.section.md b/nixos/doc/manual/development/settings-options.section.md
index 476ba4b03f9d5..5060dd98f58fc 100644
--- a/nixos/doc/manual/development/settings-options.section.md
+++ b/nixos/doc/manual/development/settings-options.section.md
@@ -143,7 +143,7 @@ These functions all return an attribute set with these values:
     :::
 
 ::: {#ex-settings-nix-representable .example}
-**Example: Module with conventional `settings` option**
+### Module with conventional `settings` option
 
 The following shows a module for an example program that uses a JSON
 configuration file. It demonstrates how above values can be used, along
@@ -218,7 +218,7 @@ the port, which will enforce it to be a valid integer and make it show
 up in the manual.
 
 ::: {#ex-settings-typed-attrs .example}
-**Example: Declaring a type-checked `settings` attribute**
+### Declaring a type-checked `settings` attribute
 ```nix
 settings = lib.mkOption {
   type = lib.types.submodule {
diff --git a/nixos/doc/manual/development/writing-modules.chapter.md b/nixos/doc/manual/development/writing-modules.chapter.md
index ae657458d7680..e07b899e6df7b 100644
--- a/nixos/doc/manual/development/writing-modules.chapter.md
+++ b/nixos/doc/manual/development/writing-modules.chapter.md
@@ -37,7 +37,7 @@ options, but does not declare any. The structure of full NixOS modules
 is shown in [Example: Structure of NixOS Modules](#ex-module-syntax).
 
 ::: {#ex-module-syntax .example}
-**Example: Structure of NixOS Modules**
+### Structure of NixOS Modules
 ```nix
 { config, pkgs, ... }:
 
@@ -100,7 +100,7 @@ Exec directives](#exec-escaping-example) for an example. When using these
 functions system environment substitution should *not* be disabled explicitly.
 
 ::: {#locate-example .example}
-**Example: NixOS Module for the "locate" Service**
+### NixOS Module for the "locate" Service
 ```nix
 { config, lib, pkgs, ... }:
 
@@ -161,7 +161,7 @@ in {
 :::
 
 ::: {#exec-escaping-example .example}
-**Example: Escaping in Exec directives**
+### Escaping in Exec directives
 ```nix
 { config, lib, pkgs, utils, ... }:
 
diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md
index 3de46fda3df67..486a4b64a262f 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/nixos/doc/manual/development/writing-nixos-tests.section.md
@@ -130,6 +130,11 @@ starting them in parallel:
 start_all()
 ```
 
+If the hostname of a node contains characters that can't be used in a
+Python variable name, those characters will be replaced with
+underscores in the variable name, so `nodes.machine-a` will be exposed
+to Python as `machine_a`.
+
 ## Machine objects {#ssec-machine-objects}
 
 The following methods are available on machine objects:
diff --git a/nixos/doc/manual/installation/installing.chapter.md b/nixos/doc/manual/installation/installing.chapter.md
index 7d67894e59f92..53cf9ed14c33a 100644
--- a/nixos/doc/manual/installation/installing.chapter.md
+++ b/nixos/doc/manual/installation/installing.chapter.md
@@ -538,7 +538,7 @@ drive (here `/dev/sda`). [Example: NixOS Configuration](#ex-config) shows a
 corresponding configuration Nix expression.
 
 ::: {#ex-partition-scheme-MBR .example}
-**Example: Example partition schemes for NixOS on `/dev/sda` (MBR)**
+### Example partition schemes for NixOS on `/dev/sda` (MBR)
 ```ShellSession
 # parted /dev/sda -- mklabel msdos
 # parted /dev/sda -- mkpart primary 1MB -8GB
@@ -547,7 +547,7 @@ corresponding configuration Nix expression.
 :::
 
 ::: {#ex-partition-scheme-UEFI .example}
-**Example: Example partition schemes for NixOS on `/dev/sda` (UEFI)**
+### Example partition schemes for NixOS on `/dev/sda` (UEFI)
 ```ShellSession
 # parted /dev/sda -- mklabel gpt
 # parted /dev/sda -- mkpart primary 512MB -8GB
@@ -558,7 +558,7 @@ corresponding configuration Nix expression.
 :::
 
 ::: {#ex-install-sequence .example}
-**Example: Commands for Installing NixOS on `/dev/sda`**
+### Commands for Installing NixOS on `/dev/sda`
 
 With a partitioned disk.
 
@@ -578,7 +578,7 @@ With a partitioned disk.
 :::
 
 ::: {#ex-config .example}
-**Example: NixOS Configuration**
+### Example: NixOS Configuration
 ```ShellSession
 { config, pkgs, ... }: {
   imports = [
diff --git a/nixos/doc/manual/preface.md b/nixos/doc/manual/preface.md
index d5e6364780a7d..b33af979c5a9f 100644
--- a/nixos/doc/manual/preface.md
+++ b/nixos/doc/manual/preface.md
@@ -4,7 +4,7 @@ This manual describes how to install, use and extend NixOS, a Linux distribution
 
 Additional information regarding the Nix package manager and the Nixpkgs project can be found in respectively the [Nix manual](https://nixos.org/nix/manual) and the [Nixpkgs manual](https://nixos.org/nixpkgs/manual).
 
-If you encounter problems, please report them on the [`Discourse`](https://discourse.nixos.org), the [Matrix room](https://matrix.to/#nix:nixos.org), or on the [`#nixos` channel on Libera.Chat](irc://irc.libera.chat/#nixos). Alternatively, consider [contributing to this manual](#chap-contributing). Bugs should be reported in [NixOS’ GitHub issue tracker](https://github.com/NixOS/nixpkgs/issues).
+If you encounter problems, please report them on the [`Discourse`](https://discourse.nixos.org), the [Matrix room](https://matrix.to/#/%23nix:nixos.org), or on the [`#nixos` channel on Libera.Chat](irc://irc.libera.chat/#nixos). Alternatively, consider [contributing to this manual](#chap-contributing). Bugs should be reported in [NixOS’ GitHub issue tracker](https://github.com/NixOS/nixpkgs/issues).
 
 ::: {.note}
 Commands prefixed with `#` have to be run as root, either requiring to login as root user or temporarily switching to it using `sudo` for example.
diff --git a/nixos/doc/manual/release-notes/rl-2305.section.md b/nixos/doc/manual/release-notes/rl-2305.section.md
index f11c865bd8f1b..510651f6464cb 100644
--- a/nixos/doc/manual/release-notes/rl-2305.section.md
+++ b/nixos/doc/manual/release-notes/rl-2305.section.md
@@ -24,16 +24,22 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - KDE Plasma has been updated to v5.27, see [the release notes](https://kde.org/announcements/plasma/5/5.27.0/) for what is changed.
 
+- Python implements [PEP 668](https://peps.python.org/pep-0668/), providing better feedback to users that try to run `pip install` system-wide.
+
 - `nixos-rebuild` now supports an extra `--specialisation` option that can be used to change specialisation for `switch` and `test` commands.
 
 - `libxcrypt`, the library providing the `crypt(3)` password hashing function, is now built without support for algorithms not flagged [`strong`](https://github.com/besser82/libxcrypt/blob/v4.4.33/lib/hashes.conf#L48). This affects the availability of password hashing algorithms used for system login (`login(1)`, `passwd(1)`), but also Apache2 Basic-Auth, Samba, OpenLDAP, Dovecot, and [many other packages](https://github.com/search?q=repo%3ANixOS%2Fnixpkgs%20libxcrypt&type=code).
 
+- `boot.bootspec.enable` (internal option) is now enabled by default because [RFC-0125](https://github.com/NixOS/rfcs/pull/125) was merged. This means you will have a bootspec document called `boot.json` generated for each system and specialisation in the top-level. This is useful to enable advanced boot usecases in NixOS such as SecureBoot.
+
 ## New Services {#sec-release-23.05-new-services}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
 - [Akkoma](https://akkoma.social), an ActivityPub microblogging server. Available as [services.akkoma](options.html#opt-services.akkoma.enable).
 
+- [Pixelfed](https://pixelfed.org/), an Instagram-like ActivityPub server. Available as [services.pixelfed](options.html#opt-services.pixelfed.enable).
+
 - [blesh](https://github.com/akinomyoga/ble.sh), a line editor written in pure bash. Available as [programs.bash.blesh](#opt-programs.bash.blesh.enable).
 
 - [webhook](https://github.com/adnanh/webhook), a lightweight webhook server. Available as [services.webhook](#opt-services.webhook.enable).
@@ -44,13 +50,22 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [Cloudlog](https://www.magicbug.co.uk/cloudlog/), a web-based Amateur Radio logging application. Available as [services.cloudlog](#opt-services.cloudlog.enable).
 
+- [Deepin Desktop Environment](https://github.com/linuxdeepin/dde), an elegant, easy to use and reliable desktop environment. Available as [services.xserver.desktopManager.deepin](options.html#opt-services.xserver.desktopManager.deepin).
+
+- [system-repart](https://www.freedesktop.org/software/systemd/man/systemd-repart.service.html), grow and add partitions to a partition table. Available as [systemd.repart](options.html#opt-systemd.repart) and [boot.initrd.systemd.repart](options.html#opt-boot.initrd.systemd.repart)
+
 - [fzf](https://github.com/junegunn/fzf), a command line fuzzyfinder. Available as [programs.fzf](#opt-programs.fzf.fuzzyCompletion).
+
 - [readarr](https://github.com/Readarr/Readarr), Book Manager and Automation (Sonarr for Ebooks). Available as [services.readarr](options.html#opt-services.readarr.enable).
 
 - [gemstash](https://github.com/rubygems/gemstash), a RubyGems.org cache and private gem server. Available as [services.gemstash](#opt-services.gemstash.enable).
 
+- [gitea-actions-runner](https://gitea.com/gitea/act_runner), a CI runner for Gitea/Forgejo Actions. Available as [services.gitea-actions-runner](#opt-services.gitea-actions-runner.instances).
+
 - [gmediarender](https://github.com/hzeller/gmrender-resurrect), a simple, headless UPnP/DLNA renderer.  Available as [services.gmediarender](options.html#opt-services.gmediarender.enable).
 
+- [harmonia](https://github.com/nix-community/harmonia/), Nix binary cache implemented in rust using libnix-store. Available as [services.harmonia](options.html#opt-services.harmonia.enable).
+
 - [hyprland](https://github.com/hyprwm/hyprland), a dynamic tiling Wayland compositor that doesn't sacrifice on its looks. Available as [programs.hyprland](#opt-programs.hyprland.enable).
 
 - [minipro](https://gitlab.com/DavidGriffith/minipro/), an open source program for controlling the MiniPRO TL866xx series of chip programmers. Available as [programs.minipro](options.html#opt-programs.minipro.enable).
@@ -63,6 +78,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [opensearch](https://opensearch.org), a search server alternative to Elasticsearch. Available as [services.opensearch](options.html#opt-services.opensearch.enable).
 
+- [kavita](https://kavitareader.com), a self-hosted digital library. Available as [services.kavita](options.html#opt-services.kavita.enable).
+
+- [monica](https://www.monicahq.com), an open source personal CRM. Available as [services.monica](options.html#opt-services.monica.enable).
+
 - [authelia](https://www.authelia.com/), is an open-source authentication and authorization server. Available under [services.authelia](options.html#opt-services.authelia.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).
@@ -77,22 +96,42 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [networkd-dispatcher](https://gitlab.com/craftyguy/networkd-dispatcher), a dispatcher service for systemd-networkd connection status changes. Available as [services.networkd-dispatcher](#opt-services.networkd-dispatcher.enable).
 
+- [gonic](https://github.com/sentriz/gonic), a Subsonic music streaming server. Available as [services.gonic](#opt-services.gonic.enable).
+
 - [mmsd](https://gitlab.com/kop316/mmsd), a lower level daemon that transmits and recieves MMSes. Available as [services.mmsd](#opt-services.mmsd.enable).
 
 - [QDMR](https://dm3mat.darc.de/qdmr/), a GUI application and command line tool for programming DMR radios [programs.qdmr](#opt-programs.qdmr.enable)
 
 - [keyd](https://github.com/rvaiya/keyd), a key remapping daemon for linux. Available as [services.keyd](#opt-services.keyd.enable).
 
+- [consul-template](https://github.com/hashicorp/consul-template/), a template rendering, notifier, and supervisor for HashiCorp Consul and Vault data. Available as [services.consul-template](#opt-services.consul-template.instances).
+
+- [vault-agent](https://developer.hashicorp.com/vault/docs/agent), a template rendering and API auth proxy for HashiCorp Vault, similar to `consul-template`. Available as [services.vault-agent](#opt-services.vault-agent.instances).
+
+- [trippy](https://github.com/fujiapple852/trippy), a network diagnostic tool. Available as [programs.trippy](#opt-programs.trippy.enable).
+
 - [v2rayA](https://v2raya.org), a Linux web GUI client of Project V which supports V2Ray, Xray, SS, SSR, Trojan and Pingtunnel. Available as [services.v2raya](options.html#opt-services.v2raya.enable).
 
+- [rshim](https://github.com/Mellanox/rshim-user-space), the user-space rshim driver for the BlueField SoC. Available as [services.rshim](options.html#opt-services.rshim.enable).
+
 - [wstunnel](https://github.com/erebe/wstunnel), a proxy tunnelling arbitrary TCP or UDP traffic through a WebSocket connection. Instances may be configured via [services.wstunnel](options.html#opt-services.wstunnel.enable).
 
 - [ulogd](https://www.netfilter.org/projects/ulogd/index.html), a userspace logging daemon for netfilter/iptables related logging. Available as [services.ulogd](options.html#opt-services.ulogd.enable).
 
+- [PufferPanel](https://pufferpanel.com), game server management panel designed to be easy to use. Available as [services.pufferpanel](#opt-services.pufferpanel.enable).
+
 - [jellyseerr](https://github.com/Fallenbagel/jellyseerr), a web-based requests manager for Jellyfin, forked from Overseerr. Available as [services.jellyseerr](#opt-services.jellyseerr.enable).
 
+- [stargazer](https://sr.ht/~zethra/stargazer/), a fast and easy to use Gemini server. Available as [services.stargazer](#opt-services.stargazer.enable).
+
+- [sniffnet](https://github.com/GyulyVGC/sniffnet), an application to monitor your network traffic. Available as [programs.sniffnet](#opt-programs.sniffnet.enable).
+
 - [photoprism](https://photoprism.app/), a AI-Powered Photos App for the Decentralized Web. Available as [services.photoprism](options.html#opt-services.photoprism.enable).
 
+- [alice-lg](github.com/alice-lg/alice-lg), a looking-glass for BGP sessions. Available as [services.alice-lg](#opt-services.alice-lg.enable).
+
+- [birdwatcher](github.com/alice-lg/birdwatcher), a small HTTP server meant to provide an API defined by Barry O'Donovan's birds-eye to the BIRD internet routing daemon. Available as [services.birdwatcher](#opt-services.birdwatcher.enable).
+
 - [peroxide](https://github.com/ljanyst/peroxide), a fork of the official [ProtonMail bridge](https://github.com/ProtonMail/proton-bridge) that aims to be similar to [Hydroxide](https://github.com/emersion/hydroxide). Available as [services.peroxide](#opt-services.peroxide.enable).
 
 - [autosuspend](https://github.com/languitar/autosuspend), a python daemon that suspends a system if certain conditions are met, or not met.
@@ -103,22 +142,30 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - [trurl](https://github.com/curl/trurl), a command line tool for URL parsing and manipulation.
 
+- [wgautomesh](https://git.deuxfleurs.fr/Deuxfleurs/wgautomesh), a simple utility to help connect wireguard nodes together in a full mesh topology. Available as [services.wgautomesh](options.html#opt-services.wgautomesh.enable).
+
 - [woodpecker-agents](https://woodpecker-ci.org/), a simple CI engine with great extensibility. Available as [services.woodpecker-agents](#opt-services.woodpecker-agents.agents._name_.enable).
 
 - [woodpecker-server](https://woodpecker-ci.org/), a simple CI engine with great extensibility. Available as [services.woodpecker-server](#opt-services.woodpecker-server.enable).
 
+- [lldap](https://github.com/lldap/lldap), a lightweight authentication server that provides an opinionated, simplified LDAP interface for authentication. Available as [services.lldap](#opt-services.lldap.enable).
+
 - [ReGreet](https://github.com/rharish101/ReGreet), a clean and customizable greeter for greetd. Available as [programs.regreet](#opt-programs.regreet.enable).
 
 - [v4l2-relayd](https://git.launchpad.net/v4l2-relayd), a streaming relay for v4l2loopback using gstreamer. Available as [services.v4l2-relayd](#opt-services.v4l2-relayd.instances._name_.enable).
 
 - [hardware.ipu6](#opt-hardware.ipu6.enable) adds support for ipu6 based webcams on intel tiger lake and alder lake.
 
+- [ivpn](https://www.ivpn.net/), a secure, private VPN with fast WireGuard connections. Available as [services.ivpn](#opt-services.ivpn.enable).
+
 ## Backward Incompatibilities {#sec-release-23.05-incompatibilities}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
 - `carnix` and `cratesIO` has been removed due to being unmaintained, use alternatives such as [naersk](https://github.com/nix-community/naersk) and [crate2nix](https://github.com/kolloch/crate2nix) instead.
 
+- `services.asusd` configuration now uses strings instead of structured configuration, as upstream switched to the [RON](https://github.com/ron-rs/ron) configuration format. Support for structured configuration may return when [RON](https://github.com/ron-rs/ron) generation is implemented in nixpkgs.
+
 - `checkInputs` have been renamed to `nativeCheckInputs`, because they behave the same as `nativeBuildInputs` when `doCheck` is set. `checkInputs` now denote a new type of dependencies, added to `buildInputs` when `doCheck` is set. As a rule of thumb, `nativeCheckInputs` are tools on `$PATH` used during the tests, and `checkInputs` are libraries which are linked to executables built as part of the tests. Similarly, `installCheckInputs` are renamed to `nativeInstallCheckInputs`, corresponding to `nativeBuildInputs`, and `installCheckInputs` are a new type of dependencies added to `buildInputs` when `doInstallCheck` is set. (Note that this change will not cause breakage to derivations with `strictDeps` unset, which are most packages except python, rust, ocaml and go packages).
 
 - `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`.
@@ -135,6 +182,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `git-bug` has been updated to at least version 0.8.0, which includes backwards incompatible changes. The `git-bug-migration` package can be used to upgrade existing repositories.
 
+- `graylog` has been updated to version 5, which can not be upgraded 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.
+
 - `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).
 
 - `keepassx` and `keepassx2` have been removed, due to upstream [stopping development](https://www.keepassx.org/index.html%3Fp=636.html). Consider [KeePassXC](https://keepassxc.org) as a maintained alternative.
@@ -150,6 +199,26 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `services.sourcehut.dispatch` and the corresponding package (`sourcehut.dispatchsrht`) have been removed due to [upstream deprecation](https://sourcehut.org/blog/2022-08-01-dispatch-deprecation-plans/).
 
+- The attributes used by `services.snapper.configs.<name>` have changed. Migrate from this:
+
+  ```nix
+  services.snapper.configs.example = {
+    subvolume = "/example";
+    extraConfig = ''
+      ALLOW_USERS="alice"
+    '';
+  };
+  ```
+
+  to this:
+
+  ```nix
+  services.snapper.configs.example = {
+    SUBVOLUME = "/example";
+    ALLOW_USERS = [ "alice" ];
+  };
+  ```
+
 - The [services.snapserver.openFirewall](#opt-services.snapserver.openFirewall) module option default value has been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall.
 
 - The [services.tmate-ssh-server.openFirewall](#opt-services.tmate-ssh-server.openFirewall) module option default value has been changed from `true` to `false`. You will need to explicitly set this option to `true`, or configure your firewall.
@@ -158,6 +227,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The option `i18n.inputMethod.fcitx5.enableRimeData` has been removed. Default RIME data is now included in `fcitx5-rime` by default, and can be customized using `fcitx5-rime.override { rimeDataPkgs = [ pkgs.rime-data, package2, ... ]; }`
 
+- The udev hwdb.bin file is now built with systemd-hwdb rather than the [deprecated "udevadm hwdb"](https://github.com/systemd/systemd/pull/25714). This may impact mappings where the same key is defined in multiple matching entries. The updated behavior will select the latest definition in case of conflict. In general, this should be a positive change, as the hwdb source files are designed with this ordering in mind. As an example, the mapping of the HP Dev One keyboard scan code for "mute mic" is corrected by this update. This change may impact users who have worked-around previously incorrect mappings.
+
 - Kime has been updated from 2.5.6 to 3.0.2 and the `i18n.inputMethod.kime.config` option has been removed. Users should use `daemonModules`, `iconColor`, and `extraConfig` options under `i18n.inputMethod.kime` instead.
 
 - `tut` has been updated from 1.0.34 to 2.0.0, and now uses the TOML format for the configuration file instead of INI. Additional information can be found [here](https://github.com/RasmusLindroth/tut/releases/tag/2.0.0).
@@ -180,8 +251,12 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - Calling `makeSetupHook` without passing a `name` argument is deprecated.
 
+- Top-level buildPlatform,hostPlatform,targetPlatform have been deprecated, use stdenv.X instead.
+
 - `lib.systems.examples.ghcjs` and consequently `pkgsCross.ghcjs` now use the target triplet `javascript-unknown-ghcjs` instead of `js-unknown-ghcjs`. This has been done to match an [upstream decision](https://gitlab.haskell.org/ghc/ghc/-/commit/6636b670233522f01d002c9b97827d00289dbf5c) to follow Cabal's platform naming more closely. Nixpkgs will also reject `js` as an architecture name.
 
+- `dokuwiki` has been updated from 2023-07-31a (Igor) to 2023-04-04 (Jack Jackrum), which has [completely removed](https://www.dokuwiki.org/changes#release_2023-04-04_jack_jackrum) the options to embed HTML and PHP for security reasons. The [htmlok plugin](https://www.dokuwiki.org/plugin:htmlok) can be used to regain this functionality.
+
 - The old unsupported version 6.x of the ELK-stack and Elastic beats have been removed. Use OpenSearch instead.
 
 - The `cosmoc` package has been removed. The upstream scripts in `cosmocc` should be used instead.
@@ -190,6 +265,10 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The [services.wordpress.sites.&lt;name&gt;.plugins](#opt-services.wordpress.sites._name_.plugins) and [services.wordpress.sites.&lt;name&gt;.themes](#opt-services.wordpress.sites._name_.themes) options have been converted from sets to attribute sets to allow for consumers to specify explicit install paths via attribute name.
 
+- [`services.nextcloud.database.createLocally`](#opt-services.nextcloud.database.createLocally) now uses socket authentication and is no longer compatible with password authentication.
+  - If you want the module to manage the database for you, unset [`services.nextcloud.config.dbpassFile`](#opt-services.nextcloud.config.dbpassFile) (and [`services.nextcloud.config.dbhost`](#opt-services.nextcloud.config.dbhost), if it's set).
+  - If you want to use password authentication **and** create the database locally, you will have to use [`services.mysql`](#opt-services.mysql.enable) to set it up.
+
 - `protonmail-bridge` package has been updated to major version 3.
 
 - Nebula now runs as a system user and group created for each nebula network, using the `CAP_NET_ADMIN` ambient capability on launch rather than starting as root. Ensure that any files each Nebula instance needs to access are owned by the correct user and group, by default `nebula-${networkName}`.
@@ -217,10 +296,18 @@ In addition to numerous new and upgraded packages, this release has the followin
   [upstream's release notes](https://github.com/iputils/iputils/releases/tag/20221126)
   for more details and available replacements.
 
+- The ppp plugin `rp-pppoe.so` has been renamed to `pppoe.so` in ppp 2.4.9. Starting from ppp 2.5.0, there is no longer a alias for backwards compatiblity. Configurations that use this plugin must be updated accordingly from `plugin rp-pppoe.so` to `plugin pppoe.so`. See [upstream change](https://github.com/ppp-project/ppp/commit/610a7bd76eb1f99f22317541b35001b1e24877ed).
+
 - [services.xserver.videoDrivers](options.html#opt-services.xserver.videoDrivers) now defaults to the `modesetting` driver over device-specific ones. The `radeon`, `amdgpu` and `nouveau` drivers are still available, but effectively unmaintained and not recommended for use.
 
+- [services.xserver.libinput.enable](options.html#opt-services.xserver.libinput.enable) is now set by default, enabling the more actively maintained and consistently behaved input device driver.
+
 - To enable the HTTP3 (QUIC) protocol for a nginx virtual host, set the `quic` attribute on it to true, e.g. `services.nginx.virtualHosts.<name>.quic = true;`.
 
+- In `services.fail2ban`, `bantime-increment.<name>` options now default to `null` (except `bantime-increment.enable`) and are used to set the corresponding option in `jail.local` only if not `null`. Also, enforce that `bantime-increment.formula` and `bantime-increment.multipliers` are not both specified.
+
+- The default Asterisk package was changed to v20 from v19. Asterisk versions 16 and 19 have been dropped due to being EOL. You may need to update /var/lib/asterisk to match the template files in `${asterisk-20}/var/lib/asterisk`.
+
 - conntrack helper autodetection has been removed from kernels 6.0 and up upstream, and an assertion was added to ensure things don't silently stop working. Migrate your configuration to assign helpers explicitly or use an older LTS kernel branch as a temporary workaround.
 
 - The `services.pipewire.config` options have been removed, as they have basically never worked correctly. All behavior defined by the default configuration can be overridden with drop-in files as necessary - see [below](#sec-release-23.05-migration-pipewire) for details.
@@ -235,11 +322,17 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - The `baget` package and module was removed due to being unmaintained.
 
+- The `qlandkartegt` and `garmindev` packages were removed due to being unmaintained and insecure.
+
 - `go-ethereum` package has been updated to v1.11.5 and the `puppeth` command is no longer available as of v1.11.0.
 
 - The `pnpm` package has be updated to from version 7.29.1 to version 8.1.1 and Node.js 14 support has been discontinued (though, there are workarounds if Node.js 14 is still required)
   - Migration instructions: ["Before updating pnpm to v8 in your CI, regenerate your pnpm-lock.yaml. To upgrade your lockfile, run pnpm install and commit the changes. Existing dependencies will not be updated; however, due to configuration changes in pnpm v8, some missing peer dependencies may be added to the lockfile and some packages may get deduplicated. You can commit the new lockfile even before upgrading Node.js in the CI, as pnpm v7 already supports the new lockfile format."](https://github.com/pnpm/pnpm/releases/tag/v8.0.0)
 
+- The `zplug` package changes its output path from `$out` to `$out/share/zplug`. Users should update their dependency on `${pkgs.zplug}/init.zsh` to `${pkgs.zplug}/share/zplug/init.zsh`.
+
+- The `pict-rs` package was updated from an 0.3 alpha release to 0.3 stable, and related environment variables now require two underscores instead of one.
+
 ## Other Notable Changes {#sec-release-23.05-notable-changes}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
@@ -264,7 +357,7 @@ In addition to numerous new and upgraded packages, this release has the followin
   - `services.openssh.ciphers` to `services.openssh.settings.Ciphers`
   - `services.openssh.gatewayPorts` to `services.openssh.settings.GatewayPorts`
 
-- `netbox` was updated to 3.4. NixOS' `services.netbox.package` still defaults to 3.3 if `stateVersion` is earlier than 23.05. Please review upstream's [breaking changes](https://github.com/netbox-community/netbox/releases/tag/v3.4.0), and upgrade NetBox by changing `services.netbox.package`. Database migrations will be run automatically.
+- `netbox` was updated to 3.5. NixOS' `services.netbox.package` still defaults to 3.3 if `stateVersion` is earlier than 23.05. Please review upstream's breaking changes [for 3.4.0](https://github.com/netbox-community/netbox/releases/tag/v3.4.0) and [for 3.5.0](https://github.com/netbox-community/netbox/releases/tag/v3.5.0), and upgrade NetBox by changing `services.netbox.package`. Database migrations will be run automatically.
 
 - `services.netbox` now support RFC42-style options, through `services.netbox.settings`.
 
@@ -284,16 +377,21 @@ In addition to numerous new and upgraded packages, this release has the followin
   replacement. It stores backups as volume dump files and thus better integrates
   into contemporary backup solutions.
 
-- `services.maddy` now allows to configure users and their credentials using `services.maddy.ensureCredentials`.
+- `services.maddy` got several updates:
+  - Configuration of users and their credentials using `services.maddy.ensureCredentials`.
+  - TLS configuration is now possible via `services.maddy.tls` with two loaders present: ACME and file based.
 
 - The `dnsmasq` service now takes configuration via the
   `services.dnsmasq.settings` attribute set. The option
   `services.dnsmasq.extraConfig` will be deprecated when NixOS 22.11 reaches
   end of life.
 
-- The `dokuwiki` service now takes configuration via the `services.dokuwiki.sites.<name>.settings` attribute set, `extraConfig` is deprecated and will be removed.
-  The `{aclUse,superUser,disableActions}` attributes have been renamed, `pluginsConfig` now also accepts an attribute set of booleans, passing plain PHP is deprecated.
-  Same applies to `acl` which now also accepts structured settings.
+- `kube3d` has now been renamed to `k3d` since the 3d editor that originally took that name has been dropped from nixpkgs. `kube3d` will continue to work as an alias for now.
+
+- The `dokuwiki` service is now configured via `services.dokuwiki.sites.<name>.settings` attribute set; `extraConfig` has been removed.
+  The `{aclUse,superUser,disableActions}` attributes have been renamed accordingly. `pluginsConfig` now only accepts an attribute set of booleans.
+  Passing plain PHP is no longer possible.
+  Same applies to `acl` which now also only accepts structured `settings`.
 
 - The `zsh` package changes the way to set environment variables on NixOS systems where `programs.zsh.enable` equals `false`.  It now sources `/etc/set-environment` when reading the system-level `zshenv` file.  Before, it sourced `/etc/profile` when reading the system-level `zprofile` file.
 
@@ -316,6 +414,28 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `nextcloud` has an option to enable SSE-C in S3.
 
+- NixOS swap partitions with random encryption can now control the sector size, cipher, and key size used to setup the plain encryption device over the
+  underlying block device rather than allowing them to be determined by `cryptsetup(8)`. One can use these features like so:
+
+  ```nix
+  {
+    swapDevices = [
+      {
+        device = "/dev/disk/by-partlabel/swapspace";
+
+        randomEncryption = {
+          enable = true;
+          cipher = "aes-xts-plain64";
+          keySize = 512;
+          sectorSize = 4096;
+        };
+      }
+    ];
+  }
+  ```
+
+- New option `security.pam.zfs` to enable unlocking and mounting of encrypted ZFS home dataset at login.
+
 - `services.peertube` now requires you to specify the secret file `secrets.secretsFile`. It can be generated by running `openssl rand -hex 32`.
   Before upgrading, read the release notes for PeerTube:
     - [Release v5.0.0](https://github.com/Chocobozzz/PeerTube/releases/tag/v5.0.0)
@@ -334,6 +454,8 @@ In addition to numerous new and upgraded packages, this release has the followin
   }
   ```
 
+- `services.netdata` offers a `deadlineBeforeStopSec` option which enable users who have netdata instance that takes time to initialize to not have systemd kill them for no reason.
+
 - `services.dhcpcd` service now don't solicit or accept IPv6 Router Advertisements on interfaces that use static IPv6 addresses.
   If network uses both IPv6 Unique local addresses (ULA) and global IPv6 address auto-configuration with SLAAC, must add the parameter `networking.dhcpcd.IPv6rs = true;`.
 
@@ -421,6 +543,8 @@ In addition to numerous new and upgraded packages, this release has the followin
 
 - `k3s` can now be configured with an EnvironmentFile for its systemd service, allowing secrets to be provided without ending up in the Nix Store.
 
+- `gitea` module options have been changed to be RFC042 conforming (i.e. some options were moved to be located under `services.gitea.settings`)
+
 - `boot.initrd.luks.device.<name>` has a new `tryEmptyPassphrase` option, this is useful for OEM's who need to install an encrypted disk with a future settable passphrase
 
 - Lisp gained a [manual section](https://nixos.org/manual/nixpkgs/stable/#lisp), documenting a new and backwards incompatible interface. The previous interface will be removed in a future release.
diff --git a/nixos/lib/eval-cacheable-options.nix b/nixos/lib/eval-cacheable-options.nix
index c3ba2ce663758..d26967ebe09b8 100644
--- a/nixos/lib/eval-cacheable-options.nix
+++ b/nixos/lib/eval-cacheable-options.nix
@@ -33,6 +33,7 @@ let
     ];
     specialArgs = {
       inherit config pkgs utils;
+      class = "nixos";
     };
   };
   docs = import "${nixosPath}/doc/manual" {
diff --git a/nixos/lib/eval-config-minimal.nix b/nixos/lib/eval-config-minimal.nix
index d45b9ffd42618..036389121973d 100644
--- a/nixos/lib/eval-config-minimal.nix
+++ b/nixos/lib/eval-config-minimal.nix
@@ -38,6 +38,7 @@ let
   #       is experimental.
   lib.evalModules {
     inherit prefix modules;
+    class = "nixos";
     specialArgs = {
       modulesPath = builtins.toString ../modules;
     } // specialArgs;
diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index 1e086271e5236..058ab7280ccc3 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -38,6 +38,8 @@ let pkgs_ = pkgs;
 in
 
 let
+  inherit (lib) optional;
+
   evalModulesMinimal = (import ./default.nix {
     inherit lib;
     # Implicit use of feature is noted in implementation.
@@ -47,15 +49,19 @@ let
   pkgsModule = rec {
     _file = ./eval-config.nix;
     key = _file;
-    config = {
-      # Explicit `nixpkgs.system` or `nixpkgs.localSystem` should override
-      # this.  Since the latter defaults to the former, the former should
-      # default to the argument. That way this new default could propagate all
-      # they way through, but has the last priority behind everything else.
-      nixpkgs.system = lib.mkIf (system != null) (lib.mkDefault system);
-
-      _module.args.pkgs = lib.mkIf (pkgs_ != null) (lib.mkForce pkgs_);
-    };
+    config = lib.mkMerge (
+      (optional (system != null) {
+        # Explicit `nixpkgs.system` or `nixpkgs.localSystem` should override
+        # this.  Since the latter defaults to the former, the former should
+        # default to the argument. That way this new default could propagate all
+        # they way through, but has the last priority behind everything else.
+        nixpkgs.system = lib.mkDefault system;
+      })
+      ++
+      (optional (pkgs_ != null) {
+        _module.args.pkgs = lib.mkForce pkgs_;
+      })
+    );
   };
 
   withWarnings = x:
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index db53bb98ee4ef..33d834e36b44e 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -511,7 +511,7 @@ let format' = format; in let
     ${if format == "raw" then ''
       mv $diskImage $out/${filename}
     '' else ''
-      ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
+      ${pkgs.qemu-utils}/bin/qemu-img convert -f raw -O ${format} ${compress} $diskImage $out/${filename}
     ''}
     diskImage=$out/${filename}
   '';
diff --git a/nixos/lib/make-multi-disk-zfs-image.nix b/nixos/lib/make-multi-disk-zfs-image.nix
index ecbde44971a99..077bb8f227075 100644
--- a/nixos/lib/make-multi-disk-zfs-image.nix
+++ b/nixos/lib/make-multi-disk-zfs-image.nix
@@ -261,8 +261,8 @@ let
           mv $bootDiskImage $out/${bootFilename}
           mv $rootDiskImage $out/${rootFilename}
         '' else ''
-          ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $bootDiskImage $out/${bootFilename}
-          ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
+          ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $bootDiskImage $out/${bootFilename}
+          ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
         ''}
           bootDiskImage=$out/${bootFilename}
           rootDiskImage=$out/${rootFilename}
diff --git a/nixos/lib/make-single-disk-zfs-image.nix b/nixos/lib/make-single-disk-zfs-image.nix
index ef3b1c0e2842d..a3564f9a8b68e 100644
--- a/nixos/lib/make-single-disk-zfs-image.nix
+++ b/nixos/lib/make-single-disk-zfs-image.nix
@@ -244,7 +244,7 @@ let
             ${if formatOpt == "raw" then ''
             mv $rootDiskImage $out/${rootFilename}
           '' else ''
-            ${pkgs.qemu}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
+            ${pkgs.qemu_kvm}/bin/qemu-img convert -f raw -O ${formatOpt} ${compress} $rootDiskImage $out/${rootFilename}
           ''}
             rootDiskImage=$out/${rootFilename}
             set -x
diff --git a/nixos/lib/test-driver/test_driver/driver.py b/nixos/lib/test-driver/test_driver/driver.py
index ad52f365737c1..ea6ba4b65b56c 100644
--- a/nixos/lib/test-driver/test_driver/driver.py
+++ b/nixos/lib/test-driver/test_driver/driver.py
@@ -2,6 +2,7 @@ from contextlib import contextmanager
 from pathlib import Path
 from typing import Any, Dict, Iterator, List, Union, Optional, Callable, ContextManager
 import os
+import re
 import tempfile
 
 from test_driver.logger import rootlog
@@ -28,6 +29,10 @@ def get_tmp_dir() -> Path:
     return tmp_dir
 
 
+def pythonize_name(name: str) -> str:
+    return re.sub(r"^[^A-z_]|[^A-z0-9_]", "_", name)
+
+
 class Driver:
     """A handle to the driver that sets up the environment
     and runs the tests"""
@@ -113,7 +118,7 @@ class Driver:
             polling_condition=self.polling_condition,
             Machine=Machine,  # for typing
         )
-        machine_symbols = {m.name: m for m in self.machines}
+        machine_symbols = {pythonize_name(m.name): m for m in self.machines}
         # If there's exactly one machine, make it available under the name
         # "machine", even if it's not called that.
         if len(self.machines) == 1:
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index 9de98c217a583..4b34ac423d1eb 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -7,6 +7,7 @@ import io
 import os
 import queue
 import re
+import select
 import shlex
 import shutil
 import socket
@@ -99,7 +100,7 @@ def _perform_ocr_on_screenshot(
         + "-blur 1x65535"
     )
 
-    tess_args = f"-c debug_file=/dev/null --psm 11"
+    tess_args = "-c debug_file=/dev/null --psm 11"
 
     cmd = f"convert {magick_args} '{screenshot_path}' 'tiff:{screenshot_path}.tiff'"
     ret = subprocess.run(cmd, shell=True, capture_output=True)
@@ -154,6 +155,7 @@ class StartCommand:
         # qemu options
         qemu_opts = (
             " -device virtio-serial"
+            # Note: virtconsole will map to /dev/hvc0 in Linux guests
             " -device virtconsole,chardev=shell"
             " -device virtio-rng-pci"
             " -serial stdio"
@@ -524,8 +526,10 @@ class Machine:
         if timeout is not None:
             timeout_str = f"timeout {timeout}"
 
+        # While sh is bash on NixOS, this is not the case for every distro.
+        # We explicitely call bash here to allow for the driver to boot other distros as well.
         out_command = (
-            f"{timeout_str} sh -c {shlex.quote(command)} | (base64 --wrap 0; echo)\n"
+            f"{timeout_str} bash -c {shlex.quote(command)} | (base64 --wrap 0; echo)\n"
         )
 
         assert self.shell
@@ -719,6 +723,15 @@ class Machine:
         self.wait_for_unit(jobname)
 
     def connect(self) -> None:
+        def shell_ready(timeout_secs: int) -> bool:
+            """We sent some data from the backdoor service running on the guest
+            to indicate that the backdoor shell is ready.
+            As soon as we read some data from the socket here, we assume that
+            our root shell is operational.
+            """
+            (ready, _, _) = select.select([self.shell], [], [], timeout_secs)
+            return bool(ready)
+
         if self.connected:
             return
 
@@ -728,8 +741,11 @@ class Machine:
             assert self.shell
 
             tic = time.time()
-            self.shell.recv(1024)
-            # TODO: Timeout
+            # TODO: do we want to bail after a set number of attempts?
+            while not shell_ready(timeout_secs=30):
+                self.log("Guest root shell did not produce any data yet...")
+
+            self.log(self.shell.recv(1024).decode())
             toc = time.time()
 
             self.log("connected to guest root shell")
@@ -950,7 +966,7 @@ class Machine:
         Prepares the machine to be reconnected which is useful if the
         machine was started with `allow_reboot = True`
         """
-        self.send_key(f"ctrl-alt-delete")
+        self.send_key("ctrl-alt-delete")
         self.connected = False
 
     def wait_for_x(self) -> None:
diff --git a/nixos/lib/testing/default.nix b/nixos/lib/testing/default.nix
index 9d4f9dbc43d76..a89f734b1e645 100644
--- a/nixos/lib/testing/default.nix
+++ b/nixos/lib/testing/default.nix
@@ -1,7 +1,10 @@
 { lib }:
 let
 
-  evalTest = module: lib.evalModules { modules = testModules ++ [ module ]; };
+  evalTest = module: lib.evalModules {
+    modules = testModules ++ [ module ];
+    class = "nixosTest";
+  };
   runTest = module: (evalTest ({ config, ... }: { imports = [ module ]; result = config.test; })).config.result;
 
   testModules = [
diff --git a/nixos/lib/testing/driver.nix b/nixos/lib/testing/driver.nix
index fb181c1d7e9ad..25759a91dda30 100644
--- a/nixos/lib/testing/driver.nix
+++ b/nixos/lib/testing/driver.nix
@@ -21,29 +21,20 @@ let
     in
     nodesList ++ lib.optional (lib.length nodesList == 1 && !lib.elem "machine" nodesList) "machine";
 
-  # TODO: This is an implementation error and needs fixing
-  # the testing famework cannot legitimately restrict hostnames further
-  # beyond RFC1035
-  invalidNodeNames = lib.filter
-    (node: builtins.match "^[A-z_]([A-z0-9_]+)?$" node == null)
-    nodeHostNames;
+  pythonizeName = name:
+    let
+      head = lib.substring 0 1 name;
+      tail = lib.substring 1 (-1) name;
+    in
+      (if builtins.match "[A-z_]" head == null then "_" else head) +
+      lib.stringAsChars (c: if builtins.match "[A-z0-9_]" c == null then "_" else c) tail;
 
   uniqueVlans = lib.unique (builtins.concatLists vlans);
   vlanNames = map (i: "vlan${toString i}: VLan;") uniqueVlans;
-  machineNames = map (name: "${name}: Machine;") nodeHostNames;
+  pythonizedNames = map pythonizeName nodeHostNames;
+  machineNames = map (name: "${name}: Machine;") pythonizedNames;
 
-  withChecks =
-    if lib.length invalidNodeNames > 0 then
-      throw ''
-        Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})!
-        All machines are referenced as python variables in the testing framework which will break the
-        script when special characters are used.
-
-        This is an IMPLEMENTATION ERROR and needs to be fixed. Meanwhile,
-        please stick to alphanumeric chars and underscores as separation.
-      ''
-    else
-      lib.warnIf config.skipLint "Linting is disabled";
+  withChecks = lib.warnIf config.skipLint "Linting is disabled";
 
   driver =
     hostPkgs.runCommand "nixos-test-driver-${config.name}"
@@ -87,7 +78,7 @@ let
         ${testDriver}/bin/generate-driver-symbols
         ${lib.optionalString (!config.skipLint) ''
           PYFLAKES_BUILTINS="$(
-            echo -n ${lib.escapeShellArg (lib.concatStringsSep "," nodeHostNames)},
+            echo -n ${lib.escapeShellArg (lib.concatStringsSep "," pythonizedNames)},
             < ${lib.escapeShellArg "driver-symbols"}
           )" ${hostPkgs.python3Packages.pyflakes}/bin/pyflakes $out/test-script
         ''}
diff --git a/nixos/lib/testing/nodes.nix b/nixos/lib/testing/nodes.nix
index c538ab468c526..6e439fd814db7 100644
--- a/nixos/lib/testing/nodes.nix
+++ b/nixos/lib/testing/nodes.nix
@@ -1,13 +1,22 @@
 testModuleArgs@{ config, lib, hostPkgs, nodes, ... }:
 
 let
-  inherit (lib) mkOption mkForce optional types mapAttrs mkDefault mdDoc;
-
-  system = hostPkgs.stdenv.hostPlatform.system;
+  inherit (lib)
+    literalExpression
+    literalMD
+    mapAttrs
+    mdDoc
+    mkDefault
+    mkIf
+    mkOption mkForce
+    optional
+    optionalAttrs
+    types
+    ;
 
   baseOS =
     import ../eval-config.nix {
-      inherit system;
+      system = null; # use modularly defined system
       inherit (config.node) specialArgs;
       modules = [ config.defaults ];
       baseModules = (import ../../modules/module-list.nix) ++
@@ -17,11 +26,17 @@ let
           ({ config, ... }:
             {
               virtualisation.qemu.package = testModuleArgs.config.qemu.package;
-
+            })
+          (optionalAttrs (!config.node.pkgsReadOnly) {
+            key = "nodes.nix-pkgs";
+            config = {
               # Ensure we do not use aliases. Ideally this is only set
               # when the test framework is used by Nixpkgs NixOS tests.
               nixpkgs.config.allowAliases = false;
-            })
+              # TODO: switch to nixpkgs.hostPlatform and make sure containers-imperative test still evaluates.
+              nixpkgs.system = hostPkgs.stdenv.hostPlatform.system;
+            };
+          })
           testModuleArgs.config.extraBaseModules
         ];
     };
@@ -68,6 +83,30 @@ in
       default = { };
     };
 
+    node.pkgs = mkOption {
+      description = mdDoc ''
+        The Nixpkgs to use for the nodes.
+
+        Setting this will make the `nixpkgs.*` options read-only, to avoid mistakenly testing with a Nixpkgs configuration that diverges from regular use.
+      '';
+      type = types.nullOr types.pkgs;
+      default = null;
+      defaultText = literalMD ''
+        `null`, so construct `pkgs` according to the `nixpkgs.*` options as usual.
+      '';
+    };
+
+    node.pkgsReadOnly = mkOption {
+      description = mdDoc ''
+        Whether to make the `nixpkgs.*` options read-only. This is only relevant when [`node.pkgs`](#test-opt-node.pkgs) is set.
+
+        Set this to `false` when any of the [`nodes`](#test-opt-nodes) needs to configure any of the `nixpkgs.*` options. This will slow down evaluation of your test a bit.
+      '';
+      type = types.bool;
+      default = config.node.pkgs != null;
+      defaultText = literalExpression ''node.pkgs != null'';
+    };
+
     node.specialArgs = mkOption {
       type = types.lazyAttrsOf types.raw;
       default = { };
@@ -100,5 +139,11 @@ in
         config.nodes;
 
     passthru.nodes = config.nodesCompat;
+
+    defaults = mkIf config.node.pkgsReadOnly {
+      nixpkgs.pkgs = config.node.pkgs;
+      imports = [ ../../modules/misc/nixpkgs/read-only.nix ];
+    };
+
   };
 }
diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix
index 0db0f4d0dcca4..6f6bab8dc3d66 100644
--- a/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -102,8 +102,8 @@ in {
        ${pkgs.jq}/bin/jq -n \
          --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
          --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
-         --arg root_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
-         --arg boot_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg boot_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$bootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
          --arg boot_mode "${amiBootMode}" \
          --arg root "$rootDisk" \
          --arg boot "$bootDisk" \
@@ -142,7 +142,7 @@ in {
        ${pkgs.jq}/bin/jq -n \
          --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
          --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
-         --arg logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+         --arg logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$diskImage" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
          --arg boot_mode "${amiBootMode}" \
          --arg file "$diskImage" \
           '{}
diff --git a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
index f73e251d30469..936dcee12949e 100644
--- a/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
+++ b/nixos/maintainers/scripts/openstack/openstack-image-zfs.nix
@@ -85,7 +85,7 @@ in
         ${pkgs.jq}/bin/jq -n \
           --arg system_label ${lib.escapeShellArg config.system.nixos.label} \
           --arg system ${lib.escapeShellArg pkgs.stdenv.hostPlatform.system} \
-          --arg root_logical_bytes "$(${pkgs.qemu}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
+          --arg root_logical_bytes "$(${pkgs.qemu_kvm}/bin/qemu-img info --output json "$rootDisk" | ${pkgs.jq}/bin/jq '."virtual-size"')" \
           --arg boot_mode "${imageBootMode}" \
           --arg root "$rootDisk" \
          '{}
diff --git a/nixos/modules/config/gnu.nix b/nixos/modules/config/gnu.nix
index d06b479e2af53..a47d299b226b5 100644
--- a/nixos/modules/config/gnu.nix
+++ b/nixos/modules/config/gnu.nix
@@ -29,7 +29,6 @@
 
     # GNU GRUB, where available.
     boot.loader.grub.enable = !pkgs.stdenv.isAarch32;
-    boot.loader.grub.version = 2;
 
     # GNU lsh.
     services.openssh.enable = false;
diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
index b740ebfccb20d..ae0661f472f69 100644
--- a/nixos/modules/config/malloc.nix
+++ b/nixos/modules/config/malloc.nix
@@ -30,7 +30,7 @@ let
 
       systemPlatform = platformMap.${pkgs.stdenv.hostPlatform.system} or (throw "scudo not supported on ${pkgs.stdenv.hostPlatform.system}");
     in {
-      libPath = "${pkgs.llvmPackages_latest.compiler-rt}/lib/linux/libclang_rt.scudo-${systemPlatform}.so";
+      libPath = "${pkgs.llvmPackages_14.compiler-rt}/lib/linux/libclang_rt.scudo-${systemPlatform}.so";
       description = ''
         A user-mode allocator based on LLVM Sanitizer’s CombinedAllocator,
         which aims at providing additional mitigations against heap based
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 3ebe2fa9f1647..dac09bdf468a9 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -47,7 +47,7 @@ with lib;
       libva = super.libva-minimal;
       limesuite = super.limesuite.override { withGui = false; };
       mc = super.mc.override { x11Support = false; };
-      mpv-unwrapped = super.mpv-unwrapped.override { sdl2Support = false; x11Support = false; };
+      mpv-unwrapped = super.mpv-unwrapped.override { sdl2Support = false; x11Support = false; waylandSupport = false; };
       msmtp = super.msmtp.override { withKeyring = false; };
       neofetch = super.neofetch.override { x11Support = false; };
       networkmanager-fortisslvpn = super.networkmanager-fortisslvpn.override { withGnome = false; };
@@ -59,6 +59,7 @@ with lib;
       networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
       pango = super.pango.override { x11Support = false; };
       pinentry = super.pinentry.override { enabledFlavors = [ "curses" "tty" "emacs" ]; withLibsecret = false; };
+      pipewire = super.pipewire.override { x11Support = false; };
       qemu = super.qemu.override { gtkSupport = false; spiceSupport = false; sdlSupport = false; };
       qrencode = super.qrencode.overrideAttrs (_: { doCheck = false; });
       qt5 = super.qt5.overrideScope (const (super': {
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
index 2c9c4c9c1a2d8..5c812a9226e84 100644
--- a/nixos/modules/config/swap.nix
+++ b/nixos/modules/config/swap.nix
@@ -38,6 +38,34 @@ let
         '';
       };
 
+      keySize = mkOption {
+        default = null;
+        example = "512";
+        type = types.nullOr types.int;
+        description = lib.mdDoc ''
+          Set the encryption key size for the plain device.
+
+          If not specified, the amount of data to read from `source` will be
+          determined by cryptsetup.
+
+          See `cryptsetup-open(8)` for details.
+        '';
+      };
+
+      sectorSize = mkOption {
+        default = null;
+        example = "4096";
+        type = types.nullOr types.int;
+        description = lib.mdDoc ''
+          Set the sector size for the plain encrypted device type.
+
+          If not specified, the default sector size is determined from the
+          underlying block device.
+
+          See `cryptsetup-open(8)` for details.
+        '';
+      };
+
       source = mkOption {
         default = "/dev/urandom";
         example = "/dev/random";
@@ -157,11 +185,11 @@ let
 
     };
 
-    config = rec {
+    config = {
       device = mkIf options.label.isDefined
         "/dev/disk/by-label/${config.label}";
       deviceName = lib.replaceStrings ["\\"] [""] (escapeSystemdPath config.device);
-      realDevice = if config.randomEncryption.enable then "/dev/mapper/${deviceName}" else config.device;
+      realDevice = if config.randomEncryption.enable then "/dev/mapper/${config.deviceName}" else config.device;
     };
 
   };
@@ -247,7 +275,12 @@ in
                 ''}
                 ${optionalString sw.randomEncryption.enable ''
                   cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \
-                    ${optionalString sw.randomEncryption.allowDiscards "--allow-discards"} ${sw.device} ${sw.deviceName}
+                '' + concatMapStrings (arg: arg + " \\\n") (flatten [
+                  (optional (sw.randomEncryption.sectorSize != null) "--sector-size=${toString sw.randomEncryption.sectorSize}")
+                  (optional (sw.randomEncryption.keySize != null) "--key-size=${toString sw.randomEncryption.keySize}")
+                  (optional sw.randomEncryption.allowDiscards "--allow-discards")
+                ]) + ''
+                  ${sw.device} ${sw.deviceName}
                   mkswap ${sw.realDevice}
                 ''}
               '';
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index e44cce11f3a8b..d1e9c8072eac4 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -428,6 +428,8 @@ let
 
   uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid";
   gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
+  sdInitrdUidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) config.boot.initrd.systemd.users) "uid";
+  sdInitrdGidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) config.boot.initrd.systemd.groups) "gid";
 
   spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
     inherit (cfg) mutableUsers;
@@ -534,6 +536,54 @@ in {
         WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing.
       '';
     };
+
+    # systemd initrd
+    boot.initrd.systemd.users = mkOption {
+      visible = false;
+      description = ''
+        Users to include in initrd.
+      '';
+      default = {};
+      type = types.attrsOf (types.submodule ({ name, ... }: {
+        options.uid = mkOption {
+          visible = false;
+          type = types.int;
+          description = ''
+            ID of the user in initrd.
+          '';
+          defaultText = literalExpression "config.users.users.\${name}.uid";
+          default = cfg.users.${name}.uid;
+        };
+        options.group = mkOption {
+          visible = false;
+          type = types.singleLineStr;
+          description = ''
+            Group the user belongs to in initrd.
+          '';
+          defaultText = literalExpression "config.users.users.\${name}.group";
+          default = cfg.users.${name}.group;
+        };
+      }));
+    };
+
+    boot.initrd.systemd.groups = mkOption {
+      visible = false;
+      description = ''
+        Groups to include in initrd.
+      '';
+      default = {};
+      type = types.attrsOf (types.submodule ({ name, ... }: {
+        options.gid = mkOption {
+          visible = false;
+          type = types.int;
+          description = ''
+            ID of the group in initrd.
+          '';
+          defaultText = literalExpression "config.users.groups.\${name}.gid";
+          default = cfg.groups.${name}.gid;
+        };
+      }));
+    };
   };
 
 
@@ -639,10 +689,52 @@ in {
       "/etc/profiles/per-user/$USER"
     ];
 
+    # systemd initrd
+    boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
+      contents = {
+        "/etc/passwd".text = ''
+          ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group }: let
+            g = config.boot.initrd.systemd.groups.${group};
+          in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:") config.boot.initrd.systemd.users)}
+        '';
+        "/etc/group".text = ''
+          ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
+        '';
+      };
+
+      users = {
+        root = {};
+        nobody = {};
+      };
+
+      groups = {
+        root = {};
+        nogroup = {};
+        systemd-journal = {};
+        tty = {};
+        dialout = {};
+        kmem = {};
+        input = {};
+        video = {};
+        render = {};
+        sgx = {};
+        audio = {};
+        video = {};
+        lp = {};
+        disk = {};
+        cdrom = {};
+        tape = {};
+        kvm = {};
+      };
+    };
+
     assertions = [
       { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
         message = "UIDs and GIDs must be unique!";
       }
+      { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
+        message = "systemd initrd UIDs and GIDs must be unique!";
+      }
       { # If mutableUsers is false, to prevent users creating a
         # configuration that locks them out of the system, ensure that
         # there is at least one "privileged" account that has a
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index 520db128acd9f..2a35afad2ac76 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -10,10 +10,7 @@ let
     check = x: (lib.types.package.check x) && (attrByPath ["meta" "isIbusEngine"] false x);
   };
 
-  impanel =
-    if cfg.panel != null
-    then "--panel=${cfg.panel}"
-    else "";
+  impanel = optionalString (cfg.panel != null) "--panel=${cfg.panel}";
 
   ibusAutostart = pkgs.writeTextFile {
     name = "autostart-ibus-daemon";
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 8fa070b03db31..e22bb866927ba 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -22,8 +22,8 @@ let
       (option: ''
         menuentry '${defaults.name} ${
         # Name appended to menuentry defaults to params if no specific name given.
-        option.name or (if option ? params then "(${option.params})" else "")
-        }' ${if option ? class then " --class ${option.class}" else ""} {
+        option.name or (optionalString (option ? params) "(${option.params})")
+        }' ${optionalString (option ? class) " --class ${option.class}"} {
           linux ${defaults.image} \''${isoboot} ${defaults.params} ${
             option.params or ""
           }
@@ -473,7 +473,7 @@ in
     };
 
     isoImage.squashfsCompression = mkOption {
-      default = with pkgs.stdenv.targetPlatform; "xz -Xdict-size 100% "
+      default = with pkgs.stdenv.hostPlatform; "xz -Xdict-size 100% "
                 + lib.optionalString isx86 "-Xbcj x86"
                 # Untested but should also reduce size for these platforms
                 + lib.optionalString isAarch "-Xbcj arm"
@@ -483,6 +483,7 @@ in
         Compression settings to use for the squashfs nix store.
       '';
       example = "zstd -Xcompression-level 6";
+      type = types.str;
     };
 
     isoImage.edition = mkOption {
@@ -693,8 +694,6 @@ in
       }
     ];
 
-    boot.loader.grub.version = 2;
-
     # Don't build the GRUB menu builder script, since we don't need it
     # here and it causes a cyclic dependency.
     boot.loader.grub.enable = false;
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index 03bb529cd8510..a55c0ab2d6557 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -8,6 +8,20 @@ with lib;
 {
   options = {
 
+    netboot.squashfsCompression = mkOption {
+      default = with pkgs.stdenv.hostPlatform; "xz -Xdict-size 100% "
+                + lib.optionalString isx86 "-Xbcj x86"
+                # Untested but should also reduce size for these platforms
+                + lib.optionalString isAarch "-Xbcj arm"
+                + lib.optionalString (isPower && is32bit && isBigEndian) "-Xbcj powerpc"
+                + lib.optionalString (isSparc) "-Xbcj sparc";
+      description = lib.mdDoc ''
+        Compression settings to use for the squashfs nix store.
+      '';
+      example = "zstd -Xcompression-level 6";
+      type = types.str;
+    };
+
     netboot.storeContents = mkOption {
       example = literalExpression "[ pkgs.stdenv ]";
       description = lib.mdDoc ''
@@ -77,6 +91,7 @@ with lib;
     # Create the squashfs image that contains the Nix store.
     system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
       storeContents = config.netboot.storeContents;
+      comp = config.netboot.squashfsCompression;
     };
 
 
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 74972c0994bed..5d3d0216d20c9 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -85,12 +85,7 @@ sub debug {
 
 
 # nixpkgs.system
-my ($status, @systemLines) = runCommand("@nixInstantiate@ --impure --eval --expr builtins.currentSystem");
-if ($status != 0 || join("", @systemLines) =~ /error/) {
-    die "Failed to retrieve current system type from nix.\n";
-}
-chomp(my $system = @systemLines[0]);
-push @attrs, "nixpkgs.hostPlatform = lib.mkDefault $system;";
+push @attrs, "nixpkgs.hostPlatform = lib.mkDefault \"@system@\";";
 
 
 my $cpuinfo = read_file "/proc/cpuinfo";
@@ -200,7 +195,7 @@ sub pciCheck {
     }
 
     # In case this is a virtio scsi device, we need to explicitly make this available.
-    if ($vendor eq "0x1af4" && $device eq "0x1004") {
+    if ($vendor eq "0x1af4" && ($device eq "0x1004" || $device eq "0x1048") ) {
         push @initrdAvailableKernelModules, "virtio_scsi";
     }
 
@@ -656,7 +651,6 @@ EOF
             $bootLoaderConfig = <<EOF;
   # Use the GRUB 2 boot loader.
   boot.loader.grub.enable = true;
-  boot.loader.grub.version = 2;
   # boot.loader.grub.efiSupport = true;
   # boot.loader.grub.efiInstallAsRemovable = true;
   # boot.loader.efi.efiSysMountPoint = "/boot/efi";
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 08278d3943f39..5133ad18f4bb5 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -25,6 +25,7 @@ let
     path = makeBinPath [
       pkgs.jq
       nixos-enter
+      pkgs.util-linuxMinimal
     ];
   };
 
@@ -34,7 +35,7 @@ let
     name = "nixos-generate-config";
     src = ./nixos-generate-config.pl;
     perl = "${pkgs.perl.withPackages (p: [ p.FileSlurp ])}/bin/perl";
-    nixInstantiate = "${pkgs.nix}/bin/nix-instantiate";
+    system = pkgs.stdenv.hostPlatform.system;
     detectvirt = "${config.systemd.package}/bin/systemd-detect-virt";
     btrfs = "${pkgs.btrfs-progs}/bin/btrfs";
     inherit (config.system.nixos-generate-config) configuration desktopConfiguration;
@@ -65,6 +66,9 @@ let
     name = "nixos-enter";
     src = ./nixos-enter.sh;
     inherit (pkgs) runtimeShell;
+    path = makeBinPath [
+      pkgs.util-linuxMinimal
+    ];
   };
 
 in
@@ -123,7 +127,7 @@ in
     system.nixos-generate-config.configuration = mkDefault ''
       # Edit this configuration file to define what should be installed on
       # your system.  Help is available in the configuration.nix(5) man page
-      # and in the NixOS manual (accessible by running ‘nixos-help’).
+      # and in the NixOS manual (accessible by running `nixos-help`).
 
       { config, pkgs, ... }:
 
@@ -214,7 +218,7 @@ in
 
         # This value determines the NixOS release from which the default
         # settings for stateful data, like file locations and database versions
-        # on your system were taken. It’s perfectly fine and recommended to leave
+        # on your system were taken. It's perfectly fine and recommended to leave
         # this value at the release version of the first install of this system.
         # Before changing this value read the documentation for this option
         # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index e0c6af4abe106..31486a2216ad0 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -38,6 +38,7 @@ let
           modules = [ {
             _module.check = false;
           } ] ++ docModules.eager;
+          class = "nixos";
           specialArgs = specialArgs // {
             pkgs = scrubDerivations "pkgs" pkgs;
             # allow access to arbitrary options for eager modules, eg for getting
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index 7f44c3f6f3f0e..55ec08acf4453 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -49,10 +49,10 @@ let
     merge = lib.mergeOneOption;
   };
 
-  pkgsType = mkOptionType {
-    name = "nixpkgs";
+  pkgsType = types.pkgs // {
+    # This type is only used by itself, so let's elaborate the description a bit
+    # for the purpose of documentation.
     description = "An evaluation of Nixpkgs; the top level attribute set of packages";
-    check = builtins.isAttrs;
   };
 
   # Whether `pkgs` was constructed by this module - not if nixpkgs.pkgs or
diff --git a/nixos/modules/misc/nixpkgs/read-only.nix b/nixos/modules/misc/nixpkgs/read-only.nix
new file mode 100644
index 0000000000000..2a783216a9d54
--- /dev/null
+++ b/nixos/modules/misc/nixpkgs/read-only.nix
@@ -0,0 +1,74 @@
+# A replacement for the traditional nixpkgs module, such that none of the modules
+# can add their own configuration. This ensures that the Nixpkgs configuration is
+# exactly as the user intends.
+# This may also be used as a performance optimization when evaluating multiple
+# configurations at once, with a shared `pkgs`.
+
+# This is a separate module, because merging this logic into the nixpkgs module
+# is too burdensome, considering that it is already burdened with legacy.
+# Moving this logic into a module does not lose any composition benefits, because
+# its purpose is not something that composes anyway.
+
+{ lib, config, ... }:
+
+let
+  cfg = config.nixpkgs;
+  inherit (lib) mkOption types;
+
+in
+{
+  disabledModules = [
+    ../nixpkgs.nix
+  ];
+  options = {
+    nixpkgs = {
+      pkgs = mkOption {
+        type = lib.types.pkgs;
+        description = lib.mdDoc ''The pkgs module argument.'';
+      };
+      config = mkOption {
+        internal = true;
+        type = types.unique { message = "nixpkgs.config is set to read-only"; } types.anything;
+        description = lib.mdDoc ''
+          The Nixpkgs `config` that `pkgs` was initialized with.
+        '';
+      };
+      overlays = mkOption {
+        internal = true;
+        type = types.unique { message = "nixpkgs.overlays is set to read-only"; } types.anything;
+        description = lib.mdDoc ''
+          The Nixpkgs overlays that `pkgs` was initialized with.
+        '';
+      };
+      hostPlatform = mkOption {
+        internal = true;
+        readOnly = true;
+        description = lib.mdDoc ''
+          The platform of the machine that is running the NixOS configuration.
+        '';
+      };
+      buildPlatform = mkOption {
+        internal = true;
+        readOnly = true;
+        description = lib.mdDoc ''
+          The platform of the machine that built the NixOS configuration.
+        '';
+      };
+      # NOTE: do not add the legacy options such as localSystem here. Let's keep
+      #       this module simple and let module authors upgrade their code instead.
+    };
+  };
+  config = {
+    _module.args.pkgs =
+      # find mistaken definitions
+      builtins.seq cfg.config
+      builtins.seq cfg.overlays
+      builtins.seq cfg.hostPlatform
+      builtins.seq cfg.buildPlatform
+      cfg.pkgs;
+    nixpkgs.config = cfg.pkgs.config;
+    nixpkgs.overlays = cfg.pkgs.overlays;
+    nixpkgs.hostPlatform = cfg.pkgs.stdenv.hostPlatform;
+    nixpkgs.buildPlatform = cfg.pkgs.stdenv.buildPlatform;
+  };
+}
diff --git a/nixos/modules/misc/nixpkgs/test.nix b/nixos/modules/misc/nixpkgs/test.nix
index a6d8877ae0700..0536cfc9624a2 100644
--- a/nixos/modules/misc/nixpkgs/test.nix
+++ b/nixos/modules/misc/nixpkgs/test.nix
@@ -1,3 +1,5 @@
+# [nixpkgs]$ nix-build -A nixosTests.nixpkgs --show-trace
+
 { evalMinimalConfig, pkgs, lib, stdenv }:
 let
   eval = mod: evalMinimalConfig {
@@ -27,6 +29,47 @@ let
     let
       uncheckedEval = lib.evalModules { modules = [ ../nixpkgs.nix module ]; };
     in map (ass: ass.message) (lib.filter (ass: !ass.assertion) uncheckedEval.config.assertions);
+
+  readOnlyUndefined = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+  };
+
+  readOnlyBad = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = { };
+  };
+
+  readOnly = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+  };
+
+  readOnlyBadConfig = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+    nixpkgs.config.allowUnfree = true; # do in pkgs instead!
+  };
+
+  readOnlyBadOverlays = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+    nixpkgs.overlays = [ (_: _: {}) ]; # do in pkgs instead!
+  };
+
+  readOnlyBadHostPlatform = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+    nixpkgs.hostPlatform = "foo-linux"; # do in pkgs instead!
+  };
+
+  readOnlyBadBuildPlatform = evalMinimalConfig {
+    imports = [ ./read-only.nix ];
+    nixpkgs.pkgs = pkgs;
+    nixpkgs.buildPlatform = "foo-linux"; # do in pkgs instead!
+  };
+
+  throws = x: ! (builtins.tryEval x).success;
+
 in
 lib.recurseIntoAttrs {
   invokeNixpkgsSimple =
@@ -65,5 +108,21 @@ lib.recurseIntoAttrs {
         nixpkgs.pkgs = pkgs;
       } == [];
 
+
+    # Tests for the read-only.nix module
+    assert readOnly._module.args.pkgs.stdenv.hostPlatform.system == pkgs.stdenv.hostPlatform.system;
+    assert throws readOnlyBad._module.args.pkgs.stdenv;
+    assert throws readOnlyUndefined._module.args.pkgs.stdenv;
+    assert throws readOnlyBadConfig._module.args.pkgs.stdenv;
+    assert throws readOnlyBadOverlays._module.args.pkgs.stdenv;
+    assert throws readOnlyBadHostPlatform._module.args.pkgs.stdenv;
+    assert throws readOnlyBadBuildPlatform._module.args.pkgs.stdenv;
+    # read-only.nix does not provide legacy options, for the sake of simplicity
+    # If you're bothered by this, upgrade your configs to use the new *Platform
+    # options.
+    assert !readOnly.options.nixpkgs?system;
+    assert !readOnly.options.nixpkgs?localSystem;
+    assert !readOnly.options.nixpkgs?crossSystem;
+
     pkgs.emptyFile;
 }
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index f44dc588ee03f..cefdb3133cbc9 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -153,6 +153,7 @@
   ./programs/cnping.nix
   ./programs/command-not-found/command-not-found.nix
   ./programs/criu.nix
+  ./programs/darling.nix
   ./programs/dconf.nix
   ./programs/digitalbitbox/default.nix
   ./programs/dmrconfig.nix
@@ -171,6 +172,7 @@
   ./programs/fuse.nix
   ./programs/fzf.nix
   ./programs/gamemode.nix
+  ./programs/gamescope.nix
   ./programs/geary.nix
   ./programs/git.nix
   ./programs/gnome-disks.nix
@@ -233,6 +235,7 @@
   ./programs/singularity.nix
   ./programs/skim.nix
   ./programs/slock.nix
+  ./programs/sniffnet.nix
   ./programs/spacefm.nix
   ./programs/ssh.nix
   ./programs/starship.nix
@@ -246,6 +249,7 @@
   ./programs/thunar.nix
   ./programs/tmux.nix
   ./programs/traceroute.nix
+  ./programs/trippy.nix
   ./programs/tsm-client.nix
   ./programs/turbovnc.nix
   ./programs/udevil.nix
@@ -305,6 +309,7 @@
   ./services/audio/alsa.nix
   ./services/audio/botamusique.nix
   ./services/audio/gmediarender.nix
+  ./services/audio/gonic.nix
   ./services/audio/hqplayerd.nix
   ./services/audio/icecast.nix
   ./services/audio/jack.nix
@@ -370,6 +375,7 @@
   ./services/continuous-integration/buildbot/master.nix
   ./services/continuous-integration/buildbot/worker.nix
   ./services/continuous-integration/buildkite-agents.nix
+  ./services/continuous-integration/gitea-actions-runner.nix
   ./services/continuous-integration/github-runner.nix
   ./services/continuous-integration/github-runners.nix
   ./services/continuous-integration/gitlab-runner.nix
@@ -395,6 +401,7 @@
   ./services/databases/hbase-standalone.nix
   ./services/databases/influxdb.nix
   ./services/databases/influxdb2.nix
+  ./services/databases/lldap.nix
   ./services/databases/memcached.nix
   ./services/databases/monetdb.nix
   ./services/databases/mongodb.nix
@@ -411,6 +418,9 @@
   ./services/desktops/bamf.nix
   ./services/desktops/blueman.nix
   ./services/desktops/cpupower-gui.nix
+  ./services/desktops/deepin/dde-api.nix
+  ./services/desktops/deepin/app-services.nix
+  ./services/desktops/deepin/dde-daemon.nix
   ./services/desktops/dleyna-renderer.nix
   ./services/desktops/dleyna-server.nix
   ./services/desktops/espanso.nix
@@ -439,6 +449,7 @@
   ./services/desktops/pipewire/wireplumber.nix
   ./services/desktops/profile-sync-daemon.nix
   ./services/desktops/system-config-printer.nix
+  ./services/desktops/system76-scheduler.nix
   ./services/desktops/telepathy.nix
   ./services/desktops/tumbler.nix
   ./services/desktops/zeitgeist.nix
@@ -670,6 +681,7 @@
   ./services/misc/polaris.nix
   ./services/misc/portunus.nix
   ./services/misc/prowlarr.nix
+  ./services/misc/pufferpanel.nix
   ./services/misc/pykms.nix
   ./services/misc/radarr.nix
   ./services/misc/readarr.nix
@@ -677,6 +689,7 @@
   ./services/misc/ripple-data-api.nix
   ./services/misc/rippled.nix
   ./services/misc/rmfakecloud.nix
+  ./services/misc/rshim.nix
   ./services/misc/safeeyes.nix
   ./services/misc/sdrplay.nix
   ./services/misc/serviio.nix
@@ -794,6 +807,7 @@
   ./services/network-filesystems/yandex-disk.nix
   ./services/networking/3proxy.nix
   ./services/networking/adguardhome.nix
+  ./services/networking/alice-lg.nix
   ./services/networking/amuled.nix
   ./services/networking/antennas.nix
   ./services/networking/aria2.nix
@@ -808,6 +822,7 @@
   ./services/networking/bind.nix
   ./services/networking/bird-lg.nix
   ./services/networking/bird.nix
+  ./services/networking/birdwatcher.nix
   ./services/networking/bitcoind.nix
   ./services/networking/bitlbee.nix
   ./services/networking/blockbook-frontend.nix
@@ -866,6 +881,7 @@
   ./services/networking/gobgpd.nix
   ./services/networking/gvpe.nix
   ./services/networking/hans.nix
+  ./services/networking/harmonia.nix
   ./services/networking/haproxy.nix
   ./services/networking/headscale.nix
   ./services/networking/hostapd.nix
@@ -884,6 +900,7 @@
   ./services/networking/iscsi/initiator.nix
   ./services/networking/iscsi/root-initiator.nix
   ./services/networking/iscsi/target.nix
+  ./services/networking/ivpn.nix
   ./services/networking/iwd.nix
   ./services/networking/jibri/default.nix
   ./services/networking/jicofo.nix
@@ -957,6 +974,7 @@
   ./services/networking/pdns-recursor.nix
   ./services/networking/pdnsd.nix
   ./services/networking/peroxide.nix
+  ./services/networking/picosnitch.nix
   ./services/networking/pixiecore.nix
   ./services/networking/pleroma.nix
   ./services/networking/polipo.nix
@@ -1042,6 +1060,7 @@
   ./services/networking/wg-netmanager.nix
   ./services/networking/webhook.nix
   ./services/networking/wg-quick.nix
+  ./services/networking/wgautomesh.nix
   ./services/networking/wireguard.nix
   ./services/networking/wpa_supplicant.nix
   ./services/networking/wstunnel.nix
@@ -1103,6 +1122,7 @@
   ./services/security/torsocks.nix
   ./services/security/usbguard.nix
   ./services/security/vault.nix
+  ./services/security/vault-agent.nix
   ./services/security/vaultwarden/default.nix
   ./services/security/yubikey-agent.nix
   ./services/system/automatic-timezoned.nix
@@ -1162,6 +1182,7 @@
   ./services/web-apps/gerrit.nix
   ./services/web-apps/gotify-server.nix
   ./services/web-apps/grocy.nix
+  ./services/web-apps/pixelfed.nix
   ./services/web-apps/healthchecks.nix
   ./services/web-apps/hedgedoc.nix
   ./services/web-apps/hledger-web.nix
@@ -1173,15 +1194,18 @@
   ./services/web-apps/jirafeau.nix
   ./services/web-apps/jitsi-meet.nix
   ./services/web-apps/kasmweb/default.nix
+  ./services/web-apps/kavita.nix
   ./services/web-apps/keycloak.nix
   ./services/web-apps/komga.nix
   ./services/web-apps/lemmy.nix
   ./services/web-apps/limesurvey.nix
+  ./services/web-apps/mainsail.nix
   ./services/web-apps/mastodon.nix
   ./services/web-apps/matomo.nix
   ./services/web-apps/mattermost.nix
   ./services/web-apps/mediawiki.nix
   ./services/web-apps/miniflux.nix
+  ./services/web-apps/monica.nix
   ./services/web-apps/moodle.nix
   ./services/web-apps/netbox.nix
   ./services/web-apps/nextcloud.nix
@@ -1239,6 +1263,7 @@
   ./services/web-servers/nginx/gitweb.nix
   ./services/web-servers/phpfpm/default.nix
   ./services/web-servers/pomerium.nix
+  ./services/web-servers/stargazer.nix
   ./services/web-servers/tomcat.nix
   ./services/web-servers/traefik.nix
   ./services/web-servers/trafficserver/default.nix
@@ -1353,6 +1378,7 @@
   ./tasks/filesystems/cifs.nix
   ./tasks/filesystems/ecryptfs.nix
   ./tasks/filesystems/envfs.nix
+  ./tasks/filesystems/erofs.nix
   ./tasks/filesystems/exfat.nix
   ./tasks/filesystems/ext.nix
   ./tasks/filesystems/f2fs.nix
diff --git a/nixos/modules/programs/darling.nix b/nixos/modules/programs/darling.nix
new file mode 100644
index 0000000000000..c4e1c73b5c29a
--- /dev/null
+++ b/nixos/modules/programs/darling.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.darling;
+in {
+  options = {
+    programs.darling = {
+      enable = lib.mkEnableOption (lib.mdDoc "Darling, a Darwin/macOS compatibility layer for Linux");
+      package = lib.mkPackageOptionMD pkgs "darling" {};
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    security.wrappers.darling = {
+      source = lib.getExe cfg.package;
+      owner = "root";
+      group = "root";
+      setuid = true;
+    };
+  };
+}
diff --git a/nixos/modules/programs/firefox.nix b/nixos/modules/programs/firefox.nix
index 3a5105c57d767..ead048134d8d3 100644
--- a/nixos/modules/programs/firefox.nix
+++ b/nixos/modules/programs/firefox.nix
@@ -201,6 +201,7 @@ 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";
@@ -217,6 +218,8 @@ in
         extraPrefs = cfg.autoConfig;
         extraNativeMessagingHosts = with pkgs; optionals nmh.ff2mpv [
           ff2mpv
+        ] ++ optionals nmh.euwebid [
+          web-eid-app
         ] ++ optionals nmh.gsconnect [
           gnomeExtensions.gsconnect
         ] ++ optionals nmh.jabref [
@@ -230,6 +233,7 @@ in
     nixpkgs.config.firefox = {
       enableBrowserpass = nmh.browserpass;
       enableBukubrow = nmh.bukubrow;
+      enableEUWebID = nmh.euwebid;
       enableTridactylNative = nmh.tridactyl;
       enableUgetIntegrator = nmh.ugetIntegrator;
       enableFXCastBridge = nmh.fxCast;
diff --git a/nixos/modules/programs/fzf.nix b/nixos/modules/programs/fzf.nix
index eda4eacde4ac9..7c4f338e29b30 100644
--- a/nixos/modules/programs/fzf.nix
+++ b/nixos/modules/programs/fzf.nix
@@ -1,8 +1,9 @@
-{pkgs, config, lib, ...}:
+{ pkgs, config, lib, ... }:
 with lib;
 let
   cfg = config.programs.fzf;
-in {
+in
+{
   options = {
     programs.fzf = {
       fuzzyCompletion = mkEnableOption (mdDoc "fuzzy completion with fzf");
@@ -11,17 +12,21 @@ in {
   };
   config = {
     environment.systemPackages = optional (cfg.keybindings || cfg.fuzzyCompletion) pkgs.fzf;
+
     programs.bash.interactiveShellInit = optionalString cfg.fuzzyCompletion ''
       source ${pkgs.fzf}/share/fzf/completion.bash
     '' + optionalString cfg.keybindings ''
       source ${pkgs.fzf}/share/fzf/key-bindings.bash
     '';
 
-    programs.zsh.interactiveShellInit = optionalString cfg.fuzzyCompletion ''
-      source ${pkgs.fzf}/share/fzf/completion.zsh
-    '' + optionalString cfg.keybindings ''
-      source ${pkgs.fzf}/share/fzf/key-bindings.zsh
-    '';
+    programs.zsh.interactiveShellInit = optionalString (!config.programs.zsh.ohMyZsh.enable)
+      (optionalString cfg.fuzzyCompletion ''
+        source ${pkgs.fzf}/share/fzf/completion.zsh
+      '' + optionalString cfg.keybindings ''
+        source ${pkgs.fzf}/share/fzf/key-bindings.zsh
+      '');
+
+    programs.zsh.ohMyZsh.plugins = lib.mkIf (cfg.keybindings || cfg.fuzzyCompletion) [ "fzf" ];
   };
   meta.maintainers = with maintainers; [ laalsaas ];
 }
diff --git a/nixos/modules/programs/gamescope.nix b/nixos/modules/programs/gamescope.nix
new file mode 100644
index 0000000000000..c4424849a41ed
--- /dev/null
+++ b/nixos/modules/programs/gamescope.nix
@@ -0,0 +1,85 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+with lib; let
+  cfg = config.programs.gamescope;
+
+  gamescope =
+    let
+      wrapperArgs =
+        optional (cfg.args != [ ])
+          ''--add-flags "${toString cfg.args}"''
+        ++ builtins.attrValues (mapAttrs (var: val: "--set-default ${var} ${val}") cfg.env);
+    in
+    pkgs.runCommand "gamescope" { nativeBuildInputs = [ pkgs.makeBinaryWrapper ]; } ''
+      mkdir -p $out/bin
+      makeWrapper ${cfg.package}/bin/gamescope $out/bin/gamescope --inherit-argv0 \
+        ${toString wrapperArgs}
+    '';
+in
+{
+  options.programs.gamescope = {
+    enable = mkEnableOption (mdDoc "gamescope");
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.gamescope;
+      defaultText = literalExpression "pkgs.gamescope";
+      description = mdDoc ''
+        The GameScope package to use.
+      '';
+    };
+
+    capSysNice = mkOption {
+      type = types.bool;
+      default = false;
+      description = mdDoc ''
+        Add cap_sys_nice capability to the GameScope
+        binary so that it may renice itself.
+      '';
+    };
+
+    args = mkOption {
+      type = types.listOf types.string;
+      default = [ ];
+      example = [ "--rt" "--prefer-vk-device 8086:9bc4" ];
+      description = mdDoc ''
+        Arguments passed to GameScope on startup.
+      '';
+    };
+
+    env = mkOption {
+      type = types.attrsOf types.string;
+      default = { };
+      example = literalExpression ''
+        # for Prime render offload on Nvidia laptops.
+        # Also requires `hardware.nvidia.prime.offload.enable`.
+        {
+          __NV_PRIME_RENDER_OFFLOAD = "1";
+          __VK_LAYER_NV_optimus = "NVIDIA_only";
+          __GLX_VENDOR_LIBRARY_NAME = "nvidia";
+        }
+      '';
+      description = mdDoc ''
+        Default environment variables available to the GameScope process, overridable at runtime.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.wrappers = mkIf cfg.capSysNice {
+      gamescope = {
+        owner = "root";
+        group = "root";
+        source = "${gamescope}/bin/gamescope";
+        capabilities = "cap_sys_nice+pie";
+      };
+    };
+
+    environment.systemPackages = mkIf (!cfg.capSysNice) [ gamescope ];
+  };
+
+  meta.maintainers = with maintainers; [ nrdxp ];
+}
diff --git a/nixos/modules/programs/hyprland.nix b/nixos/modules/programs/hyprland.nix
index b14f1f77fcf8a..92b8e992e6485 100644
--- a/nixos/modules/programs/hyprland.nix
+++ b/nixos/modules/programs/hyprland.nix
@@ -57,17 +57,14 @@ in
   };
 
   config = mkIf cfg.enable {
-    environment = {
-      systemPackages = [ cfg.package ];
-
-    };
+    environment.systemPackages = [ cfg.package ];
 
     fonts.enableDefaultFonts = mkDefault true;
     hardware.opengl.enable = mkDefault true;
 
     programs = {
       dconf.enable = mkDefault true;
-      xwayland.enable = mkDefault true;
+      xwayland.enable = mkDefault cfg.xwayland.enable;
     };
 
     security.polkit.enable = true;
diff --git a/nixos/modules/programs/less.nix b/nixos/modules/programs/less.nix
index a1134e774364a..81c68307aee14 100644
--- a/nixos/modules/programs/less.nix
+++ b/nixos/modules/programs/less.nix
@@ -11,7 +11,7 @@ let
     ${concatStringsSep "\n"
       (mapAttrsToList (command: action: "${command} ${action}") cfg.commands)
     }
-    ${if cfg.clearDefaultCommands then "#stop" else ""}
+    ${optionalString cfg.clearDefaultCommands "#stop"}
 
     #line-edit
     ${concatStringsSep "\n"
diff --git a/nixos/modules/programs/miriway.nix b/nixos/modules/programs/miriway.nix
index 52b5f84762220..a67e1a17a7e60 100644
--- a/nixos/modules/programs/miriway.nix
+++ b/nixos/modules/programs/miriway.nix
@@ -8,7 +8,7 @@ in {
       Miriway, a Mir based Wayland compositor. You can manually launch Miriway by
       executing "exec miriway" on a TTY, or launch it from a display manager. Copy
       /etc/xdg/xdg-miriway/miriway-shell.config to ~/.config/miriway-shell.config
-      to modify the default configuration. See <https://github.com/Miriway/Miriway>,
+      to modify the system-wide configuration on a per-user basis. See <https://github.com/Miriway/Miriway>,
       and "miriway --help" for more information'');
 
     config = lib.mkOption {
@@ -19,6 +19,15 @@ in {
         ctrl-alt=t:miriway-terminal # Default "terminal emulator finder"
 
         shell-component=dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY
+
+        meta=Left:@dock-left
+        meta=Right:@dock-right
+        meta=Space:@toggle-maximized
+        meta=Home:@workspace-begin
+        meta=End:@workspace-end
+        meta=Page_Up:@workspace-up
+        meta=Page_Down:@workspace-down
+        ctrl-alt=BackSpace:@exit
       '';
       example = ''
         idle-timeout=300
@@ -31,6 +40,15 @@ in {
         shell-component=wbg Pictures/wallpaper
 
         shell-meta=a:synapse
+
+        meta=Left:@dock-left
+        meta=Right:@dock-right
+        meta=Space:@toggle-maximized
+        meta=Home:@workspace-begin
+        meta=End:@workspace-end
+        meta=Page_Up:@workspace-up
+        meta=Page_Down:@workspace-down
+        ctrl-alt=BackSpace:@exit
       '';
       description = lib.mdDoc ''
         Miriway's config. This will be installed system-wide.
diff --git a/nixos/modules/programs/neovim.nix b/nixos/modules/programs/neovim.nix
index 4562e5a2c29b8..1b53b9b5d9194 100644
--- a/nixos/modules/programs/neovim.nix
+++ b/nixos/modules/programs/neovim.nix
@@ -4,12 +4,8 @@ with lib;
 
 let
   cfg = config.programs.neovim;
-
-  runtime' = filter (f: f.enable) (attrValues cfg.runtime);
-
-  runtime = pkgs.linkFarm "neovim-runtime" (map (x: { name = "etc/${x.target}"; path = x.source; }) runtime');
-
-in {
+in
+{
   options.programs.neovim = {
     enable = mkOption {
       type = types.bool;
@@ -70,7 +66,7 @@ in {
 
     configure = mkOption {
       type = types.attrs;
-      default = {};
+      default = { };
       example = literalExpression ''
         {
           customRC = '''
@@ -105,7 +101,7 @@ in {
     };
 
     runtime = mkOption {
-      default = {};
+      default = { };
       example = literalExpression ''
         { "ftplugin/c.vim".text = "setlocal omnifunc=v:lua.vim.lsp.omnifunc"; }
       '';
@@ -115,14 +111,15 @@ in {
 
       type = with types; attrsOf (submodule (
         { name, config, ... }:
-        { options = {
+        {
+          options = {
 
             enable = mkOption {
               type = types.bool;
               default = true;
               description = lib.mdDoc ''
-                Whether this /etc file should be generated.  This
-                option allows specific /etc files to be disabled.
+                Whether this runtime directory should be generated.  This
+                option allows specific runtime files to be disabled.
               '';
             };
 
@@ -141,20 +138,16 @@ in {
             };
 
             source = mkOption {
-              type = types.path;
+              default = null;
+              type = types.nullOr types.path;
               description = lib.mdDoc "Path of the source file.";
             };
 
           };
 
-          config = {
-            target = mkDefault name;
-            source = mkIf (config.text != null) (
-              let name' = "neovim-runtime" + baseNameOf name;
-              in mkDefault (pkgs.writeText name' config.text));
-          };
-
-        }));
+          config.target = mkDefault name;
+        }
+      ));
 
     };
   };
@@ -165,14 +158,19 @@ in {
     ];
     environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "nvim");
 
-    programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package {
-      inherit (cfg) viAlias vimAlias withPython3 withNodeJs withRuby;
-      configure = cfg.configure // {
+    environment.etc = listToAttrs (attrValues (mapAttrs
+      (name: value: {
+        name = "xdg/nvim/${name}";
+        value = removeAttrs
+          (value // {
+            target = "xdg/nvim/${value.target}";
+          })
+          (optionals (isNull value.source) [ "source" ]);
+      })
+      cfg.runtime));
 
-        customRC = (cfg.configure.customRC or "") + ''
-          set runtimepath^=${runtime}/etc
-        '';
-      };
+    programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package {
+      inherit (cfg) viAlias vimAlias withPython3 withNodeJs withRuby configure;
     };
   };
 }
diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix
index fab809f279a36..35267acd6bb74 100644
--- a/nixos/modules/programs/shadow.nix
+++ b/nixos/modules/programs/shadow.nix
@@ -41,6 +41,8 @@ let
       # This should be made configurable.
       #CHFN_RESTRICT frwh
 
+      # The default crypt() method, keep in sync with the PAM default
+      ENCRYPT_METHOD YESCRYPT
     '';
 
   mkSetuidRoot = source:
diff --git a/nixos/modules/programs/sniffnet.nix b/nixos/modules/programs/sniffnet.nix
new file mode 100644
index 0000000000000..98e9f628a9bce
--- /dev/null
+++ b/nixos/modules/programs/sniffnet.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.sniffnet;
+in
+
+{
+  options = {
+    programs.sniffnet = {
+      enable = lib.mkEnableOption (lib.mdDoc "sniffnet");
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    security.wrappers.sniffnet = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_net_raw,cap_net_admin=eip";
+      source = "${pkgs.sniffnet}/bin/sniffnet";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ figsoda ];
+}
diff --git a/nixos/modules/programs/steam.nix b/nixos/modules/programs/steam.nix
index fc63f0f187e87..c63b31bde11f1 100644
--- a/nixos/modules/programs/steam.nix
+++ b/nixos/modules/programs/steam.nix
@@ -4,6 +4,24 @@ with lib;
 
 let
   cfg = config.programs.steam;
+  gamescopeCfg = config.programs.gamescope;
+
+  steam-gamescope = let
+    exports = builtins.attrValues (builtins.mapAttrs (n: v: "export ${n}=${v}") cfg.gamescopeSession.env);
+  in
+    pkgs.writeShellScriptBin "steam-gamescope" ''
+      ${builtins.concatStringsSep "\n" exports}
+      gamescope --steam ${toString cfg.gamescopeSession.args} -- steam -tenfoot -pipewire-dmabuf
+    '';
+
+  gamescopeSessionFile =
+    (pkgs.writeTextDir "share/wayland-sessions/steam.desktop" ''
+      [Desktop Entry]
+      Name=Steam
+      Comment=A digital distribution platform
+      Exec=${steam-gamescope}/bin/steam-gamescope
+      Type=Application
+    '').overrideAttrs (_: { passthru.providedSessions = [ "steam" ]; });
 in {
   options.programs.steam = {
     enable = mkEnableOption (lib.mdDoc "steam");
@@ -32,6 +50,12 @@ in {
             then [ package ] ++ extraPackages
             else [ package32 ] ++ extraPackages32;
         in prevLibs ++ additionalLibs;
+      } // optionalAttrs (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice)
+      {
+        buildFHSEnv = pkgs.buildFHSEnv.override {
+          # use the setuid wrapped bubblewrap
+          bubblewrap = "${config.security.wrapperDir}/..";
+        };
       });
       description = lib.mdDoc ''
         The Steam package to use. Additional libraries are added from the system
@@ -57,6 +81,31 @@ in {
         Open ports in the firewall for Source Dedicated Server.
       '';
     };
+
+    gamescopeSession = mkOption {
+      description = mdDoc "Run a GameScope driven Steam session from your display-manager";
+      default = {};
+      type = types.submodule {
+        options = {
+          enable = mkEnableOption (mdDoc "GameScope Session");
+          args = mkOption {
+            type = types.listOf types.string;
+            default = [ ];
+            description = mdDoc ''
+              Arguments to be passed to GameScope for the session.
+            '';
+          };
+
+          env = mkOption {
+            type = types.attrsOf types.string;
+            default = { };
+            description = mdDoc ''
+              Environmental variables to be passed to GameScope for the session.
+            '';
+          };
+        };
+      };
+    };
   };
 
   config = mkIf cfg.enable {
@@ -66,6 +115,19 @@ in {
       driSupport32Bit = true;
     };
 
+    security.wrappers = mkIf (cfg.gamescopeSession.enable && gamescopeCfg.capSysNice) {
+      # needed or steam fails
+      bwrap = {
+        owner = "root";
+        group = "root";
+        source = "${pkgs.bubblewrap}/bin/bwrap";
+        setuid = true;
+      };
+    };
+
+    programs.gamescope.enable = mkDefault cfg.gamescopeSession.enable;
+    services.xserver.displayManager.sessionPackages = mkIf cfg.gamescopeSession.enable [ gamescopeSessionFile ];
+
     # optionally enable 32bit pulseaudio support if pulseaudio is enabled
     hardware.pulseaudio.support32Bit = config.hardware.pulseaudio.enable;
 
@@ -74,7 +136,7 @@ in {
     environment.systemPackages = [
       cfg.package
       cfg.package.run
-    ];
+    ] ++ lib.optional cfg.gamescopeSession.enable steam-gamescope;
 
     networking.firewall = lib.mkMerge [
       (mkIf cfg.remotePlay.openFirewall {
diff --git a/nixos/modules/programs/tmux.nix b/nixos/modules/programs/tmux.nix
index 4fb9175fb8d21..4f452f1d7f9b5 100644
--- a/nixos/modules/programs/tmux.nix
+++ b/nixos/modules/programs/tmux.nix
@@ -1,7 +1,7 @@
 { config, pkgs, lib, ... }:
 
 let
-  inherit (lib) mkOption mkIf types;
+  inherit (lib) mkOption mkIf types optionalString;
 
   cfg = config.programs.tmux;
 
@@ -17,17 +17,17 @@ let
     set  -g base-index      ${toString cfg.baseIndex}
     setw -g pane-base-index ${toString cfg.baseIndex}
 
-    ${if cfg.newSession then "new-session" else ""}
+    ${optionalString cfg.newSession "new-session"}
 
-    ${if cfg.reverseSplit then ''
+    ${optionalString cfg.reverseSplit ''
     bind v split-window -h
     bind s split-window -v
-    '' else ""}
+    ''}
 
     set -g status-keys ${cfg.keyMode}
     set -g mode-keys   ${cfg.keyMode}
 
-    ${if cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize then ''
+    ${optionalString (cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize) ''
     bind h select-pane -L
     bind j select-pane -D
     bind k select-pane -U
@@ -37,15 +37,15 @@ let
     bind -r J resize-pane -D ${toString cfg.resizeAmount}
     bind -r K resize-pane -U ${toString cfg.resizeAmount}
     bind -r L resize-pane -R ${toString cfg.resizeAmount}
-    '' else ""}
+    ''}
 
-    ${if (cfg.shortcut != defaultShortcut) then ''
+    ${optionalString (cfg.shortcut != defaultShortcut) ''
     # rebind main key: C-${cfg.shortcut}
     unbind C-${defaultShortcut}
     set -g prefix C-${cfg.shortcut}
     bind ${cfg.shortcut} send-prefix
     bind C-${cfg.shortcut} last-window
-    '' else ""}
+    ''}
 
     setw -g aggressive-resize ${boolToStr cfg.aggressiveResize}
     setw -g clock-mode-style  ${if cfg.clock24 then "24" else "12"}
@@ -160,7 +160,10 @@ in {
         default = defaultTerminal;
         example = "screen-256color";
         type = types.str;
-        description = lib.mdDoc "Set the $TERM variable.";
+        description = lib.mdDoc ''
+          Set the $TERM variable. Use tmux-direct if italics or 24bit true color
+          support is needed.
+        '';
       };
 
       secureSocket = mkOption {
diff --git a/nixos/modules/programs/trippy.nix b/nixos/modules/programs/trippy.nix
new file mode 100644
index 0000000000000..6e31aea43e750
--- /dev/null
+++ b/nixos/modules/programs/trippy.nix
@@ -0,0 +1,24 @@
+{ lib, config, pkgs, ... }:
+
+let
+  cfg = config.programs.trippy;
+in
+
+{
+  options = {
+    programs.trippy = {
+      enable = lib.mkEnableOption (lib.mdDoc "trippy");
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    security.wrappers.trip = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_net_raw+p";
+      source = lib.getExe pkgs.trippy;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ figsoda ];
+}
diff --git a/nixos/modules/security/acme/default.nix b/nixos/modules/security/acme/default.nix
index ef0636258994c..8aee71c864d0a 100644
--- a/nixos/modules/security/acme/default.nix
+++ b/nixos/modules/security/acme/default.nix
@@ -323,7 +323,7 @@ let
             }
           fi
         '');
-      } // optionalAttrs (data.listenHTTP != null && toInt (elemAt (splitString ":" data.listenHTTP) 1) < 1024) {
+      } // optionalAttrs (data.listenHTTP != null && toInt (last (splitString ":" data.listenHTTP)) < 1024) {
         CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
         AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
       };
@@ -487,7 +487,7 @@ let
       };
 
       email = mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
         inherit (defaultAndText "email" null) default defaultText;
         description = lib.mdDoc ''
           Email address for account creation and correspondence from the CA.
@@ -555,7 +555,7 @@ let
       };
 
       credentialsFile = mkOption {
-        type = types.path;
+        type = types.nullOr types.path;
         inherit (defaultAndText "credentialsFile" null) default defaultText;
         description = lib.mdDoc ''
           Path to an EnvironmentFile for the cert's service containing any required and
@@ -781,11 +781,11 @@ in {
 
       # FIXME Most of these custom warnings and filters for security.acme.certs.* are required
       # because using mkRemovedOptionModule/mkChangedOptionModule with attrsets isn't possible.
-      warnings = filter (w: w != "") (mapAttrsToList (cert: data: if data.extraDomains != "_mkMergedOptionModule" then ''
+      warnings = filter (w: w != "") (mapAttrsToList (cert: data: optionalString (data.extraDomains != "_mkMergedOptionModule") ''
         The option definition `security.acme.certs.${cert}.extraDomains` has changed
         to `security.acme.certs.${cert}.extraDomainNames` and is now a list of strings.
         Setting a custom webroot for extra domains is not possible, instead use separate certs.
-      '' else "") cfg.certs);
+      '') cfg.certs);
 
       assertions = let
         certs = attrValues cfg.certs;
diff --git a/nixos/modules/security/apparmor/includes.nix b/nixos/modules/security/apparmor/includes.nix
index f290e95a296da..adfca04426cac 100644
--- a/nixos/modules/security/apparmor/includes.nix
+++ b/nixos/modules/security/apparmor/includes.nix
@@ -22,7 +22,7 @@ in
 # some may even be completely useless.
 config.security.apparmor.includes = {
   # This one is included by <tunables/global>
-  # which is usualy included before any profile.
+  # which is usually included before any profile.
   "abstractions/tunables/alias" = ''
     alias /bin -> /run/current-system/sw/bin,
     alias /lib/modules -> /run/current-system/kernel/lib/modules,
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 6e8be412de83c..eac67cfdec5a6 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -446,6 +446,15 @@ let
         };
       };
 
+      zfs = mkOption {
+        default = config.security.pam.zfs.enable;
+        defaultText = literalExpression "config.security.pam.zfs.enable";
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enable unlocking and mounting of encrypted ZFS home dataset at login.
+        '';
+      };
+
       text = mkOption {
         type = types.nullOr types.lines;
         description = lib.mdDoc "Contents of the PAM service file.";
@@ -556,7 +565,8 @@ let
               || cfg.googleAuthenticator.enable
               || cfg.gnupg.enable
               || cfg.failDelay.enable
-              || cfg.duoSecurity.enable))
+              || cfg.duoSecurity.enable
+              || cfg.zfs))
             (
               optionalString config.services.homed.enable ''
                 auth optional ${config.systemd.package}/lib/security/pam_systemd_home.so
@@ -570,6 +580,9 @@ let
               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
               '' +
@@ -628,6 +641,9 @@ let
           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
           '' +
@@ -638,7 +654,7 @@ let
             password sufficient ${pkgs.pam_mysql}/lib/security/pam_mysql.so config_file=/etc/security/pam_mysql.conf
           '' +
           optionalString config.services.sssd.enable ''
-            password sufficient ${pkgs.sssd}/lib/security/pam_sss.so use_authtok
+            password sufficient ${pkgs.sssd}/lib/security/pam_sss.so
           '' +
           optionalString config.security.pam.krb5.enable ''
             password sufficient ${pam_krb5}/lib/security/pam_krb5.so use_first_pass
@@ -685,6 +701,10 @@ let
             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
           '' +
@@ -1202,6 +1222,34 @@ in
       };
     };
 
+    security.pam.zfs = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Enable unlocking and mounting of encrypted ZFS home dataset at login.
+        '';
+      };
+
+      homes = mkOption {
+        example = "rpool/home";
+        default = "rpool/home";
+        type = types.str;
+        description = lib.mdDoc ''
+          Prefix of home datasets. This value will be concatenated with
+          `"/" + <username>` in order to determine the home dataset to unlock.
+        '';
+      };
+
+      noUnmount = mkOption {
+        default = false;
+        type = types.bool;
+        description = lib.mdDoc ''
+          Do not unmount home dataset on logout.
+        '';
+      };
+    };
+
     security.pam.enableEcryptfs = mkEnableOption (lib.mdDoc "eCryptfs PAM module (mounting ecryptfs home directory on login)");
     security.pam.enableFscrypt = mkEnableOption (lib.mdDoc ''
       Enables fscrypt to automatically unlock directories with the user's login password.
@@ -1238,6 +1286,12 @@ in
           Only one of users.motd and users.motdFile can be set.
         '';
       }
+      {
+        assertion = config.security.pam.zfs.enable -> (config.boot.zfs.enabled || config.boot.zfs.enableUnstable);
+        message = ''
+          `security.pam.zfs.enable` requires enabling ZFS (`boot.zfs.enabled` or `boot.zfs.enableUnstable`).
+        '';
+      }
     ];
 
     environment.systemPackages =
@@ -1378,7 +1432,10 @@ in
         mr ${pkgs.plasma5Packages.kwallet-pam}/lib/security/pam_kwallet5.so,
       '' +
       optionalString config.virtualisation.lxc.lxcfs.enable ''
-        mr ${pkgs.lxc}/lib/security/pam_cgfs.so
+        mr ${pkgs.lxc}/lib/security/pam_cgfs.so,
+      '' +
+      optionalString (isEnabled (cfg: cfg.zfs)) ''
+        mr ${config.boot.zfs.package}/lib/security/pam_zfs_key.so,
       '' +
       optionalString config.services.homed.enable ''
         mr ${config.systemd.package}/lib/security/pam_systemd_home.so
diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix
index 5a023cec48ee0..708c3a69d1740 100644
--- a/nixos/modules/security/tpm2.nix
+++ b/nixos/modules/security/tpm2.nix
@@ -3,7 +3,7 @@ let
   cfg = config.security.tpm2;
 
   # This snippet is taken from tpm2-tss/dist/tpm-udev.rules, but modified to allow custom user/groups
-  # The idea is that the tssUser is allowed to acess the TPM and kernel TPM resource manager, while
+  # The idea is that the tssUser is allowed to access the TPM and kernel TPM resource manager, while
   # the tssGroup is only allowed to access the kernel resource manager
   # Therefore, if either of the two are null, the respective part isn't generated
   udevRules = tssUser: tssGroup: ''
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 4b62abd658a42..12255d8392fe9 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -283,7 +283,7 @@ in
         '';
 
     ###### wrappers consistency checks
-    system.extraDependencies = lib.singleton (pkgs.runCommandLocal
+    system.checks = lib.singleton (pkgs.runCommandLocal
       "ensure-all-wrappers-paths-exist" { }
       ''
         # make sure we produce output
diff --git a/nixos/modules/services/audio/gonic.nix b/nixos/modules/services/audio/gonic.nix
new file mode 100644
index 0000000000000..65cf10f2c4b46
--- /dev/null
+++ b/nixos/modules/services/audio/gonic.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gonic;
+  settingsFormat = pkgs.formats.keyValue {
+    mkKeyValue = lib.generators.mkKeyValueDefault { } " ";
+    listsAsDuplicateKeys = true;
+  };
+in
+{
+  options = {
+    services.gonic = {
+
+      enable = mkEnableOption (lib.mdDoc "Gonic music server");
+
+      settings = mkOption rec {
+        type = settingsFormat.type;
+        apply = recursiveUpdate default;
+        default = {
+          listen-addr = "127.0.0.1:4747";
+          cache-path = "/var/cache/gonic";
+          tls-cert = null;
+          tls-key = null;
+        };
+        example = {
+          music-path = [ "/mnt/music" ];
+          podcast-path = "/mnt/podcasts";
+        };
+        description = lib.mdDoc ''
+          Configuration for Gonic, see <https://github.com/sentriz/gonic#configuration-options> for supported values.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.gonic = {
+      description = "Gonic Media Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart =
+          let
+            # these values are null by default but should not appear in the final config
+            filteredSettings = filterAttrs (n: v: !((n == "tls-cert" || n == "tls-key") && v == null)) cfg.settings;
+          in
+          "${pkgs.gonic}/bin/gonic -config-path ${settingsFormat.generate "gonic" filteredSettings}";
+        DynamicUser = true;
+        StateDirectory = "gonic";
+        CacheDirectory = "gonic";
+        WorkingDirectory = "/var/lib/gonic";
+        RuntimeDirectory = "gonic";
+        RootDirectory = "/run/gonic";
+        ReadWritePaths = "";
+        BindReadOnlyPaths = [
+          # gonic can access scrobbling services
+          "-/etc/ssl/certs/ca-certificates.crt"
+          builtins.storeDir
+          cfg.settings.podcast-path
+        ] ++ cfg.settings.music-path
+        ++ lib.optional (cfg.settings.tls-cert != null) cfg.settings.tls-cert
+        ++ lib.optional (cfg.settings.tls-key != null) cfg.settings.tls-key;
+        CapabilityBoundingSet = "";
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+        RestrictRealtime = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        UMask = "0066";
+        ProtectHostname = true;
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.autrimpo ];
+}
diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix
index e73828081d4bd..e18e61eb6d44b 100644
--- a/nixos/modules/services/audio/navidrome.nix
+++ b/nixos/modules/services/audio/navidrome.nix
@@ -11,6 +11,8 @@ in {
 
       enable = mkEnableOption (lib.mdDoc "Navidrome music server");
 
+      package = mkPackageOptionMD pkgs "navidrome" { };
+
       settings = mkOption rec {
         type = settingsFormat.type;
         apply = recursiveUpdate default;
@@ -36,7 +38,7 @@ in {
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         ExecStart = ''
-          ${pkgs.navidrome}/bin/navidrome --configfile ${settingsFormat.generate "navidrome.json" cfg.settings}
+          ${cfg.package}/bin/navidrome --configfile ${settingsFormat.generate "navidrome.json" cfg.settings}
         '';
         DynamicUser = true;
         StateDirectory = "navidrome";
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index 2af42eeb3705b..dbab741bf6fc7 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -275,9 +275,9 @@ in {
 
     warnings =
       # https://github.com/badaix/snapcast/blob/98ac8b2fb7305084376607b59173ce4097c620d8/server/streamreader/stream_manager.cpp#L85
-      filter (w: w != "") (mapAttrsToList (k: v: if v.type == "spotify" then ''
+      filter (w: w != "") (mapAttrsToList (k: v: optionalString (v.type == "spotify") ''
         services.snapserver.streams.${k}.type = "spotify" is deprecated, use services.snapserver.streams.${k}.type = "librespot" instead.
-      '' else "") cfg.streams);
+      '') cfg.streams);
 
     systemd.services.snapserver = {
       after = [ "network.target" ];
diff --git a/nixos/modules/services/backup/automysqlbackup.nix b/nixos/modules/services/backup/automysqlbackup.nix
index d0237f196a807..27bbff813b105 100644
--- a/nixos/modules/services/backup/automysqlbackup.nix
+++ b/nixos/modules/services/backup/automysqlbackup.nix
@@ -3,7 +3,7 @@
 let
 
   inherit (lib) concatMapStringsSep concatStringsSep isInt isList literalExpression;
-  inherit (lib) mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkOption optional types;
+  inherit (lib) mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkOption mkRenamedOptionModule optional types;
 
   cfg = config.services.automysqlbackup;
   pkg = pkgs.automysqlbackup;
@@ -26,6 +26,10 @@ let
 
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "automysqlbackup" "config" ] [ "services" "automysqlbackup" "settings" ])
+  ];
+
   # interface
   options = {
     services.automysqlbackup = {
@@ -40,7 +44,7 @@ in
         '';
       };
 
-      config = mkOption {
+      settings = mkOption {
         type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
         default = {};
         description = lib.mdDoc ''
@@ -112,7 +116,18 @@ in
 
     services.mysql.ensureUsers = optional (config.services.mysql.enable && cfg.config.mysql_dump_host == "localhost") {
       name = user;
-      ensurePermissions = { "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT"; };
+      ensurePermissions = {
+        "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT";
+
+        # https://forums.mysql.com/read.php?10,668311,668315#msg-668315
+        "function sys.extract_table_from_file_name" = "execute";
+        "function sys.format_path" = "execute";
+        "function sys.format_statement" = "execute";
+        "function sys.extract_schema_from_file_name" = "execute";
+        "function sys.ps_thread_account" = "execute";
+        "function sys.format_time" = "execute";
+        "function sys.format_bytes" = "execute";
+      };
     };
 
   };
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 08a2967e9c7fc..0da70112d48dd 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -109,7 +109,7 @@ let
       };
       environment = {
         BORG_REPO = cfg.repo;
-        inherit (cfg) extraArgs extraInitArgs extraCreateArgs extraPruneArgs;
+        inherit (cfg) extraArgs extraInitArgs extraCreateArgs extraPruneArgs extraCompactArgs;
       } // (mkPassEnv cfg) // cfg.environment;
     };
 
diff --git a/nixos/modules/services/backup/mysql-backup.nix b/nixos/modules/services/backup/mysql-backup.nix
index 289291c6bd2f9..9fbc599cd41af 100644
--- a/nixos/modules/services/backup/mysql-backup.nix
+++ b/nixos/modules/services/backup/mysql-backup.nix
@@ -20,7 +20,7 @@ let
   '';
   backupDatabaseScript = db: ''
     dest="${cfg.location}/${db}.gz"
-    if ${mariadb}/bin/mysqldump ${if cfg.singleTransaction then "--single-transaction" else ""} ${db} | ${gzip}/bin/gzip -c > $dest.tmp; then
+    if ${mariadb}/bin/mysqldump ${optionalString cfg.singleTransaction "--single-transaction"} ${db} | ${gzip}/bin/gzip -c > $dest.tmp; then
       mv $dest.tmp $dest
       echo "Backed up to $dest"
     else
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index ca796cf7797e6..3a951f7cbc834 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -145,6 +145,7 @@ in
           type = types.attrsOf unitOption;
           default = {
             OnCalendar = "daily";
+            Persistent = true;
           };
           description = lib.mdDoc ''
             When to run the backup. See {manpage}`systemd.timer(5)` for details.
@@ -152,6 +153,7 @@ in
           example = {
             OnCalendar = "00:05";
             RandomizedDelaySec = "5h";
+            Persistent = true;
           };
         };
 
@@ -300,7 +302,7 @@ in
             filesFromTmpFile = "/run/restic-backups-${name}/includes";
             backupPaths =
               if (backup.dynamicFilesFrom == null)
-              then if (backup.paths != null) then concatStringsSep " " backup.paths else ""
+              then optionalString (backup.paths != null) (concatStringsSep " " backup.paths)
               else "--files-from ${filesFromTmpFile}";
             pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
               (resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts))
@@ -339,6 +341,7 @@ in
               RuntimeDirectory = "restic-backups-${name}";
               CacheDirectory = "restic-backups-${name}";
               CacheDirectoryMode = "0700";
+              PrivateTmp = true;
             } // optionalAttrs (backup.environmentFile != null) {
               EnvironmentFile = backup.environmentFile;
             };
diff --git a/nixos/modules/services/blockchain/ethereum/geth.nix b/nixos/modules/services/blockchain/ethereum/geth.nix
index eca308dc366d1..d12516ca2f249 100644
--- a/nixos/modules/services/blockchain/ethereum/geth.nix
+++ b/nixos/modules/services/blockchain/ethereum/geth.nix
@@ -196,9 +196,9 @@ in
           --gcmode ${cfg.gcmode} \
           --port ${toString cfg.port} \
           --maxpeers ${toString cfg.maxpeers} \
-          ${if cfg.http.enable then ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}'' else ""} \
+          ${optionalString cfg.http.enable ''--http --http.addr ${cfg.http.address} --http.port ${toString cfg.http.port}''} \
           ${optionalString (cfg.http.apis != null) ''--http.api ${lib.concatStringsSep "," cfg.http.apis}''} \
-          ${if cfg.websocket.enable then ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}'' else ""} \
+          ${optionalString cfg.websocket.enable ''--ws --ws.addr ${cfg.websocket.address} --ws.port ${toString cfg.websocket.port}''} \
           ${optionalString (cfg.websocket.apis != null) ''--ws.api ${lib.concatStringsSep "," cfg.websocket.apis}''} \
           ${optionalString cfg.metrics.enable ''--metrics --metrics.addr ${cfg.metrics.address} --metrics.port ${toString cfg.metrics.port}''} \
           --authrpc.addr ${cfg.authrpc.address} --authrpc.port ${toString cfg.authrpc.port} --authrpc.vhosts ${lib.concatStringsSep "," cfg.authrpc.vhosts} \
diff --git a/nixos/modules/services/continuous-integration/gitea-actions-runner.nix b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
new file mode 100644
index 0000000000000..4b9046c98e8b6
--- /dev/null
+++ b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix
@@ -0,0 +1,237 @@
+{ config
+, lib
+, pkgs
+, utils
+, ...
+}:
+
+let
+  inherit (lib)
+    any
+    attrValues
+    concatStringsSep
+    escapeShellArg
+    hasInfix
+    hasSuffix
+    optionalAttrs
+    optionals
+    literalExpression
+    mapAttrs'
+    mkEnableOption
+    mkOption
+    mkPackageOptionMD
+    mkIf
+    nameValuePair
+    types
+  ;
+
+  inherit (utils)
+    escapeSystemdPath
+  ;
+
+  cfg = config.services.gitea-actions-runner;
+
+  # Check whether any runner instance label requires a container runtime
+  # Empty label strings result in the upstream defined defaultLabels, which require docker
+  # https://gitea.com/gitea/act_runner/src/tag/v0.1.5/internal/app/cmd/register.go#L93-L98
+  hasDockerScheme = instance:
+    instance.labels == [] || any (label: hasInfix ":docker:" label) instance.labels;
+  wantsContainerRuntime = any hasDockerScheme (attrValues cfg.instances);
+
+  hasHostScheme = instance: any (label: hasSuffix ":host" label) instance.labels;
+
+  # provide shorthands for whether container runtimes are enabled
+  hasDocker = config.virtualisation.docker.enable;
+  hasPodman = config.virtualisation.podman.enable;
+
+  tokenXorTokenFile = instance:
+    (instance.token == null && instance.tokenFile != null) ||
+    (instance.token != null && instance.tokenFile == null);
+in
+{
+  meta.maintainers = with lib.maintainers; [
+    hexa
+  ];
+
+  options.services.gitea-actions-runner = with types; {
+    package = mkPackageOptionMD pkgs "gitea-actions-runner" { };
+
+    instances = mkOption {
+      default = {};
+      description = lib.mdDoc ''
+        Gitea Actions Runner instances.
+      '';
+      type = attrsOf (submodule {
+        options = {
+          enable = mkEnableOption (lib.mdDoc "Gitea Actions Runner instance");
+
+          name = mkOption {
+            type = str;
+            example = literalExpression "config.networking.hostName";
+            description = lib.mdDoc ''
+              The name identifying the runner instance towards the Gitea/Forgejo instance.
+            '';
+          };
+
+          url = mkOption {
+            type = str;
+            example = "https://forge.example.com";
+            description = lib.mdDoc ''
+              Base URL of your Gitea/Forgejo instance.
+            '';
+          };
+
+          token = mkOption {
+            type = nullOr str;
+            default = null;
+            description = lib.mdDoc ''
+              Plain token to register at the configured Gitea/Forgejo instance.
+            '';
+          };
+
+          tokenFile = mkOption {
+            type = nullOr (either str path);
+            default = null;
+            description = lib.mdDoc ''
+              Path to an environment file, containing the `TOKEN` environment
+              variable, that holds a token to register at the configured
+              Gitea/Forgejo instance.
+            '';
+          };
+
+          labels = mkOption {
+            type = listOf str;
+            example = literalExpression ''
+              [
+                # provide a debian base with nodejs for actions
+                "debian-latest:docker://node:18-bullseye"
+                # fake the ubuntu name, because node provides no ubuntu builds
+                "ubuntu-latest:docker://node:18-bullseye"
+                # provide native execution on the host
+                #"native:host"
+              ]
+            '';
+            description = lib.mdDoc ''
+              Labels used to map jobs to their runtime environment. Changing these
+              labels currently requires a new registration token.
+
+              Many common actions require bash, git and nodejs, as well as a filesystem
+              that follows the filesystem hierarchy standard.
+            '';
+          };
+
+          hostPackages = mkOption {
+            type = listOf package;
+            default = with pkgs; [
+              bash
+              coreutils
+              curl
+              gawk
+              gitMinimal
+              gnused
+              nodejs
+              wget
+            ];
+            defaultText = literalExpression ''
+              with pkgs; [
+                bash
+                coreutils
+                curl
+                gawk
+                gitMinimal
+                gnused
+                nodejs
+                wget
+              ]
+            '';
+            description = lib.mdDoc ''
+              List of packages, that are available to actions, when the runner is configured
+              with a host execution label.
+            '';
+          };
+        };
+      });
+    };
+  };
+
+  config = mkIf (cfg.instances != {}) {
+    assertions = [ {
+      assertion = any tokenXorTokenFile (attrValues cfg.instances);
+      message = "Instances of gitea-actions-runner can have `token` or `tokenFile`, not both.";
+    } {
+      assertion = wantsContainerRuntime -> hasDocker || hasPodman;
+      message = "Label configuration on gitea-actions-runner instance requires either docker or podman.";
+    } ];
+
+    systemd.services = let
+      mkRunnerService = name: instance: let
+        wantsContainerRuntime = hasDockerScheme instance;
+        wantsHost = hasHostScheme instance;
+        wantsDocker = wantsContainerRuntime && config.virtualisation.docker.enable;
+        wantsPodman = wantsContainerRuntime && config.virtualisation.podman.enable;
+      in
+        nameValuePair "gitea-runner-${escapeSystemdPath name}" {
+          inherit (instance) enable;
+          description = "Gitea Actions Runner";
+          after = [
+            "network-online.target"
+          ] ++ optionals (wantsDocker) [
+            "docker.service"
+          ] ++ optionals (wantsPodman) [
+            "podman.service"
+          ];
+          wantedBy = [
+            "multi-user.target"
+          ];
+          environment = optionalAttrs (instance.token != null) {
+            TOKEN = "${instance.token}";
+          } // optionalAttrs (wantsPodman) {
+            DOCKER_HOST = "unix:///run/podman/podman.sock";
+          };
+          path = with pkgs; [
+            coreutils
+          ] ++ lib.optionals wantsHost instance.hostPackages;
+          serviceConfig = {
+            DynamicUser = true;
+            User = "gitea-runner";
+            StateDirectory = "gitea-runner";
+            WorkingDirectory = "-/var/lib/gitea-runner/${name}";
+            ExecStartPre = pkgs.writeShellScript "gitea-register-runner-${name}" ''
+              export INSTANCE_DIR="$STATE_DIRECTORY/${name}"
+              mkdir -vp "$INSTANCE_DIR"
+              cd "$INSTANCE_DIR"
+
+              # force reregistration on changed labels
+              export LABELS_FILE="$INSTANCE_DIR/.labels"
+              export LABELS_WANTED="$(echo ${escapeShellArg (concatStringsSep "\n" instance.labels)} | sort)"
+              export LABELS_CURRENT="$(cat $LABELS_FILE 2>/dev/null || echo 0)"
+
+              if [ ! -e "$INSTANCE_DIR/.runner" ] || [ "$LABELS_WANTED" != "$LABELS_CURRENT" ]; then
+                # remove existing registration file, so that changing the labels forces a re-registation
+                rm -v "$INSTANCE_DIR/.runner" || true
+
+                # perform the registration
+                ${cfg.package}/bin/act_runner register --no-interactive \
+                  --instance ${escapeShellArg instance.url} \
+                  --token "$TOKEN" \
+                  --name ${escapeShellArg instance.name} \
+                  --labels ${escapeShellArg (concatStringsSep "," instance.labels)}
+
+                # and write back the configured labels
+                echo "$LABELS_WANTED" > "$LABELS_FILE"
+              fi
+
+            '';
+            ExecStart = "${cfg.package}/bin/act_runner daemon";
+            SupplementaryGroups = optionals (wantsDocker) [
+              "docker"
+            ] ++ optionals (wantsPodman) [
+              "podman"
+            ];
+          } // optionalAttrs (instance.tokenFile != null) {
+            EnvironmentFile = instance.tokenFile;
+          };
+        };
+    in mapAttrs' mkRunnerService cfg.instances;
+  };
+}
diff --git a/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix b/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
index ef1933e12284e..e28493ce77671 100644
--- a/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
+++ b/nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
@@ -35,6 +35,9 @@ in
         ExecStartPre = testCommand;
         Restart = "on-failure";
         RestartSec = 120;
+
+        LimitSTACK = 256 * 1024 * 1024;
+        OOMPolicy = "continue";
       };
     };
 
diff --git a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
index 3a1c6c1a371df..d6a8c2a3f7cce 100644
--- a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
@@ -242,7 +242,7 @@ in {
                 jobdir="${jenkinsCfg.home}/$jenkinsjobname"
                 rm -rf "$jobdir"
             done
-          '' + (if cfg.accessUser != "" then reloadScript else "");
+          '' + (optionalString (cfg.accessUser != "") reloadScript);
       serviceConfig = {
         Type = "oneshot";
         User = jenkinsCfg.user;
diff --git a/nixos/modules/services/databases/lldap.nix b/nixos/modules/services/databases/lldap.nix
new file mode 100644
index 0000000000000..960792d0805f1
--- /dev/null
+++ b/nixos/modules/services/databases/lldap.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, utils, ... }:
+
+let
+  cfg = config.services.lldap;
+  format = pkgs.formats.toml { };
+in
+{
+  options.services.lldap = with lib; {
+    enable = mkEnableOption (mdDoc "lldap");
+
+    package = mkPackageOptionMD pkgs "lldap" { };
+
+    environment = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      example = {
+        LLDAP_JWT_SECRET_FILE = "/run/lldap/jwt_secret";
+        LLDAP_LDAP_USER_PASS_FILE = "/run/lldap/user_password";
+      };
+      description = lib.mdDoc ''
+        Environment variables passed to the service.
+        Any config option name prefixed with `LLDAP_` takes priority over the one in the configuration file.
+      '';
+    };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = lib.mdDoc ''
+        Environment file as defined in {manpage}`systemd.exec(5)` passed to the service.
+      '';
+    };
+
+    settings = mkOption {
+      description = mdDoc ''
+        Free-form settings written directly to the `lldap_config.toml` file.
+        Refer to <https://github.com/lldap/lldap/blob/main/lldap_config.docker_template.toml> for supported values.
+      '';
+
+      default = { };
+
+      type = types.submodule {
+        freeformType = format.type;
+        options = {
+          ldap_host = mkOption {
+            type = types.str;
+            description = mdDoc "The host address that the LDAP server will be bound to.";
+            default = "::";
+          };
+
+          ldap_port = mkOption {
+            type = types.port;
+            description = mdDoc "The port on which to have the LDAP server.";
+            default = 3890;
+          };
+
+          http_host = mkOption {
+            type = types.str;
+            description = mdDoc "The host address that the HTTP server will be bound to.";
+            default = "::";
+          };
+
+          http_port = mkOption {
+            type = types.port;
+            description = mdDoc "The port on which to have the HTTP server, for user login and administration.";
+            default = 17170;
+          };
+
+          http_url = mkOption {
+            type = types.str;
+            description = mdDoc "The public URL of the server, for password reset links.";
+            default = "http://localhost";
+          };
+
+          ldap_base_dn = mkOption {
+            type = types.str;
+            description = mdDoc "Base DN for LDAP.";
+            example = "dc=example,dc=com";
+          };
+
+          ldap_user_dn = mkOption {
+            type = types.str;
+            description = mdDoc "Admin username";
+            default = "admin";
+          };
+
+          ldap_user_email = mkOption {
+            type = types.str;
+            description = mdDoc "Admin email.";
+            default = "admin@example.com";
+          };
+
+          database_url = mkOption {
+            type = types.str;
+            description = mdDoc "Database URL.";
+            default = "sqlite://./users.db?mode=rwc";
+            example = "postgres://postgres-user:password@postgres-server/my-database";
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.lldap = {
+      description = "Lightweight LDAP server (lldap)";
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${lib.getExe cfg.package} run --config-file ${format.generate "lldap_config.toml" cfg.settings}";
+        StateDirectory = "lldap";
+        WorkingDirectory = "%S/lldap";
+        User = "lldap";
+        Group = "lldap";
+        DynamicUser = true;
+        EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+      };
+      inherit (cfg) environment;
+    };
+  };
+}
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 3d55995aba055..a7016bbee3a86 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -489,7 +489,7 @@ in
      "/share/postgresql"
     ];
 
-    system.extraDependencies = lib.optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) configFileCheck;
+    system.checks = lib.optional (cfg.checkConfig && pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform) configFileCheck;
 
     systemd.services.postgresql =
       { description = "PostgreSQL Server";
diff --git a/nixos/modules/services/desktops/deepin/app-services.nix b/nixos/modules/services/desktops/deepin/app-services.nix
new file mode 100644
index 0000000000000..6f9932e487336
--- /dev/null
+++ b/nixos/modules/services/desktops/deepin/app-services.nix
@@ -0,0 +1,36 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.deepin.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.deepin.app-services = {
+
+      enable = mkEnableOption (lib.mdDoc "Service collection of DDE applications, including dconfig-center");
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.deepin.app-services.enable {
+
+    environment.systemPackages = [ pkgs.deepin.dde-app-services ];
+
+    services.dbus.packages = [ pkgs.deepin.dde-app-services ];
+
+    environment.pathsToLink = [ "/share/dsg" ];
+
+  };
+
+}
diff --git a/nixos/modules/services/desktops/deepin/dde-api.nix b/nixos/modules/services/desktops/deepin/dde-api.nix
new file mode 100644
index 0000000000000..57b2290dfbc10
--- /dev/null
+++ b/nixos/modules/services/desktops/deepin/dde-api.nix
@@ -0,0 +1,50 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.deepin.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.deepin.dde-api = {
+
+      enable = mkEnableOption (lib.mdDoc ''
+        Provides some dbus interfaces that is used for screen zone detecting,
+        thumbnail generating, and sound playing in Deepin Desktop Enviroment.
+      '');
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.deepin.dde-api.enable {
+
+     environment.systemPackages = [ pkgs.deepin.dde-api ];
+
+     services.dbus.packages = [ pkgs.deepin.dde-api ];
+
+     systemd.packages = [ pkgs.deepin.dde-api ];
+
+     environment.pathsToLink = [ "/lib/deepin-api" ];
+
+     users.groups.deepin-sound-player = { };
+     users.users.deepin-sound-player = {
+       description = "Deepin sound player";
+       home = "/var/lib/deepin-sound-player";
+       createHome = true;
+       group = "deepin-sound-player";
+       isSystemUser = true;
+     };
+
+  };
+
+}
diff --git a/nixos/modules/services/desktops/deepin/dde-daemon.nix b/nixos/modules/services/desktops/deepin/dde-daemon.nix
new file mode 100644
index 0000000000000..9377f523ebf9c
--- /dev/null
+++ b/nixos/modules/services/desktops/deepin/dde-daemon.nix
@@ -0,0 +1,40 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.deepin.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.deepin.dde-daemon = {
+
+      enable = mkEnableOption (lib.mdDoc "Daemon for handling the deepin session settings");
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.deepin.dde-daemon.enable {
+
+    environment.systemPackages = [ pkgs.deepin.dde-daemon ];
+
+    services.dbus.packages = [ pkgs.deepin.dde-daemon ];
+
+    services.udev.packages = [ pkgs.deepin.dde-daemon ];
+
+    systemd.packages = [ pkgs.deepin.dde-daemon ];
+
+    environment.pathsToLink = [ "/lib/deepin-daemon" ];
+
+  };
+
+}
diff --git a/nixos/modules/services/desktops/system76-scheduler.nix b/nixos/modules/services/desktops/system76-scheduler.nix
new file mode 100644
index 0000000000000..267b528cc5dd3
--- /dev/null
+++ b/nixos/modules/services/desktops/system76-scheduler.nix
@@ -0,0 +1,296 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.system76-scheduler;
+
+  inherit (builtins) concatStringsSep map toString attrNames;
+  inherit (lib) boolToString types mkOption literalExpression mdDoc optional mkIf mkMerge;
+  inherit (types) nullOr listOf bool int ints float str enum;
+
+  withDefaults = optionSpecs: defaults:
+    lib.genAttrs (attrNames optionSpecs) (name:
+      mkOption (optionSpecs.${name} // {
+        default = optionSpecs.${name}.default or defaults.${name} or null;
+      }));
+
+  latencyProfile = withDefaults {
+    latency = {
+      type = int;
+      description = mdDoc "`sched_latency_ns`.";
+    };
+    nr-latency = {
+      type = int;
+      description = mdDoc "`sched_nr_latency`.";
+    };
+    wakeup-granularity = {
+      type = float;
+      description = mdDoc "`sched_wakeup_granularity_ns`.";
+    };
+    bandwidth-size = {
+      type = int;
+      description = mdDoc "`sched_cfs_bandwidth_slice_us`.";
+    };
+    preempt = {
+      type = enum [ "none" "voluntary" "full" ];
+      description = mdDoc "Preemption mode.";
+    };
+  };
+  schedulerProfile = withDefaults {
+    nice = {
+      type = nullOr (ints.between (-20) 19);
+      description = mdDoc "Niceness.";
+    };
+    class = {
+      type = nullOr (enum [ "idle" "batch" "other" "rr" "fifo" ]);
+      example = literalExpression "\"batch\"";
+      description = mdDoc "CPU scheduler class.";
+    };
+    prio = {
+      type = nullOr (ints.between 1 99);
+      example = literalExpression "49";
+      description = mdDoc "CPU scheduler priority.";
+    };
+    ioClass = {
+      type = nullOr (enum [ "idle" "best-effort" "realtime" ]);
+      example = literalExpression "\"best-effort\"";
+      description = mdDoc "IO scheduler class.";
+    };
+    ioPrio = {
+      type = nullOr (ints.between 0 7);
+      example = literalExpression "4";
+      description = mdDoc "IO scheduler priority.";
+    };
+    matchers = {
+      type = nullOr (listOf str);
+      default = [];
+      example = literalExpression ''
+        [
+          "include cgroup=\"/user.slice/*.service\" parent=\"systemd\""
+          "emacs"
+        ]
+      '';
+      description = mdDoc "Process matchers.";
+    };
+  };
+
+  cfsProfileToString = name: let
+    p = cfg.settings.cfsProfiles.${name};
+  in
+    "${name} latency=${toString p.latency} nr-latency=${toString p.nr-latency} wakeup-granularity=${toString p.wakeup-granularity} bandwidth-size=${toString p.bandwidth-size} preempt=\"${p.preempt}\"";
+
+  prioToString = class: prio: if prio == null then "\"${class}\"" else "(${class})${toString prio}";
+
+  schedulerProfileToString = name: a: indent:
+    concatStringsSep " "
+      (["${indent}${name}"]
+       ++ (optional (a.nice != null) "nice=${toString a.nice}")
+       ++ (optional (a.class != null) "sched=${prioToString a.class a.prio}")
+       ++ (optional (a.ioClass != null) "io=${prioToString a.ioClass a.ioPrio}")
+       ++ (optional ((builtins.length a.matchers) != 0) ("{\n${concatStringsSep "\n" (map (m: "  ${indent}${m}") a.matchers)}\n${indent}}")));
+
+in {
+  options = {
+    services.system76-scheduler = {
+      enable = lib.mkEnableOption (lib.mdDoc "system76-scheduler");
+
+      package = mkOption {
+        type = types.package;
+        default = config.boot.kernelPackages.system76-scheduler;
+        defaultText = literalExpression "config.boot.kernelPackages.system76-scheduler";
+        description = mdDoc "Which System76-Scheduler package to use.";
+      };
+
+      useStockConfig = mkOption {
+        type = bool;
+        default = true;
+        description = mdDoc ''
+          Use the (reasonable and featureful) stock configuration.
+
+          When this option is `true`, `services.system76-scheduler.settings`
+          are ignored.
+        '';
+      };
+
+      settings = {
+        cfsProfiles = {
+          enable = mkOption {
+            type = bool;
+            default = true;
+            description = mdDoc "Tweak CFS latency parameters when going on/off battery";
+          };
+
+          default = latencyProfile {
+            latency = 6;
+            nr-latency = 8;
+            wakeup-granularity = 1.0;
+            bandwidth-size = 5;
+            preempt = "voluntary";
+          };
+          responsive = latencyProfile {
+            latency = 4;
+            nr-latency = 10;
+            wakeup-granularity = 0.5;
+            bandwidth-size = 3;
+            preempt = "full";
+          };
+        };
+
+        processScheduler = {
+          enable = mkOption {
+            type = bool;
+            default = true;
+            description = mdDoc "Tweak scheduling of individual processes in real time.";
+          };
+
+          useExecsnoop = mkOption {
+            type = bool;
+            default = true;
+            description = mdDoc "Use execsnoop (otherwise poll the precess list periodically).";
+          };
+
+          refreshInterval = mkOption {
+            type = int;
+            default = 60;
+            description = mdDoc "Process list poll interval, in seconds";
+          };
+
+          foregroundBoost = {
+            enable = mkOption {
+              type = bool;
+              default = true;
+              description = mdDoc ''
+                Boost foreground process priorities.
+
+                (And de-boost background ones).  Note that this option needs cooperation
+                from the desktop environment to work.  On Gnome the client side is
+                implemented by the "System76 Scheduler" shell extension.
+              '';
+            };
+            foreground = schedulerProfile {
+              nice = 0;
+              ioClass = "best-effort";
+              ioPrio = 0;
+            };
+            background = schedulerProfile {
+              nice = 6;
+              ioClass = "idle";
+            };
+          };
+
+          pipewireBoost = {
+            enable = mkOption {
+              type = bool;
+              default = true;
+              description = mdDoc "Boost Pipewire client priorities.";
+            };
+            profile = schedulerProfile {
+              nice = -6;
+              ioClass = "best-effort";
+              ioPrio = 0;
+            };
+          };
+        };
+      };
+
+      assignments = mkOption {
+        type = types.attrsOf (types.submodule {
+          options = schedulerProfile { };
+        });
+        default = {};
+        example = literalExpression ''
+          {
+            nix-builds = {
+              nice = 15;
+              class = "batch";
+              ioClass = "idle";
+              matchers = [
+                "nix-daemon"
+              ];
+            };
+          }
+        '';
+        description = mdDoc "Process profile assignments.";
+      };
+
+      exceptions = mkOption {
+        type = types.listOf str;
+        default = [];
+        example = literalExpression ''
+          [
+            "include descends=\"schedtool\""
+            "schedtool"
+          ]
+        '';
+        description = mdDoc "Processes that are left alone.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.dbus.packages = [ cfg.package ];
+
+    systemd.services.system76-scheduler = {
+      description = "Manage process priorities and CFS scheduler latencies for improved responsiveness on the desktop";
+      wantedBy = [ "multi-user.target" ];
+      path = [
+        # execsnoop needs those to extract kernel headers:
+        pkgs.kmod
+        pkgs.gnutar
+        pkgs.xz
+      ];
+      serviceConfig = {
+        Type = "dbus";
+        BusName= "com.system76.Scheduler";
+        ExecStart = "${cfg.package}/bin/system76-scheduler daemon";
+        ExecReload = "${cfg.package}/bin/system76-scheduler daemon reload";
+      };
+    };
+
+    environment.etc = mkMerge [
+      (mkIf cfg.useStockConfig {
+        # No custom settings: just use stock configuration with a fix for Pipewire
+        "system76-scheduler/config.kdl".source = "${cfg.package}/data/config.kdl";
+        "system76-scheduler/process-scheduler/00-dist.kdl".source = "${cfg.package}/data/pop_os.kdl";
+        "system76-scheduler/process-scheduler/01-fix-pipewire-paths.kdl".source = ../../../../pkgs/os-specific/linux/system76-scheduler/01-fix-pipewire-paths.kdl;
+      })
+
+      (let
+        settings = cfg.settings;
+        cfsp = settings.cfsProfiles;
+        ps = settings.processScheduler;
+      in mkIf (!cfg.useStockConfig) {
+        "system76-scheduler/config.kdl".text = ''
+          version "2.0"
+          autogroup-enabled false
+          cfs-profiles enable=${boolToString cfsp.enable} {
+            ${cfsProfileToString "default"}
+            ${cfsProfileToString "responsive"}
+          }
+          process-scheduler enable=${boolToString ps.enable} {
+            execsnoop ${boolToString ps.useExecsnoop}
+            refresh-rate ${toString ps.refreshInterval}
+            assignments {
+              ${if ps.foregroundBoost.enable then (schedulerProfileToString "foreground" ps.foregroundBoost.foreground "    ") else ""}
+              ${if ps.foregroundBoost.enable then (schedulerProfileToString "background" ps.foregroundBoost.background "    ") else ""}
+              ${if ps.pipewireBoost.enable then (schedulerProfileToString "pipewire" ps.pipewireBoost.profile "    ") else ""}
+            }
+          }
+        '';
+      })
+
+      {
+        "system76-scheduler/process-scheduler/02-config.kdl".text =
+          "exceptions {\n${concatStringsSep "\n" (map (e: "  ${e}") cfg.exceptions)}\n}\n"
+          + "assignments {\n"
+          + (concatStringsSep "\n" (map (name: schedulerProfileToString name cfg.assignments.${name} "  ")
+            (attrNames cfg.assignments)))
+          + "\n}\n";
+      }
+    ];
+  };
+
+  meta = {
+    maintainers = [ lib.maintainers.cmm ];
+  };
+}
diff --git a/nixos/modules/services/development/lorri.nix b/nixos/modules/services/development/lorri.nix
index 8c64e3d9a5605..74f56f5890fce 100644
--- a/nixos/modules/services/development/lorri.nix
+++ b/nixos/modules/services/development/lorri.nix
@@ -50,6 +50,6 @@ in {
       };
     };
 
-    environment.systemPackages = [ cfg.package ];
+    environment.systemPackages = [ cfg.package pkgs.direnv ];
   };
 }
diff --git a/nixos/modules/services/editors/emacs.md b/nixos/modules/services/editors/emacs.md
index c072b3664ad19..72364b295144a 100644
--- a/nixos/modules/services/editors/emacs.md
+++ b/nixos/modules/services/editors/emacs.md
@@ -80,7 +80,8 @@ The first step to declare the list of packages you want in your Emacs
 installation is to create a dedicated derivation. This can be done in a
 dedicated {file}`emacs.nix` file such as:
 
-[]{#ex-emacsNix}
+::: {.example #ex-emacsNix}
+### Nix expression to build Emacs with packages (`emacs.nix`)
 
 ```nix
 /*
@@ -136,6 +137,7 @@ in
     pkgs.notmuch   # From main packages set
   ])
 ```
+:::
 
 The result of this configuration will be an {command}`emacs`
 command which launches Emacs with all of your chosen packages in the
@@ -158,19 +160,24 @@ and yasnippet.
 
 The list of available packages in the various ELPA repositories can be seen
 with the following commands:
-[]{#module-services-emacs-querying-packages}
+::: {.example #module-services-emacs-querying-packages}
+### Querying Emacs packages
+
 ```
 nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.elpaPackages
 nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.melpaPackages
 nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.melpaStablePackages
 nix-env -f "<nixpkgs>" -qaP -A emacs.pkgs.orgPackages
 ```
+:::
 
 If you are on NixOS, you can install this particular Emacs for all users by
 adding it to the list of system packages (see
 [](#sec-declarative-package-mgmt)). Simply modify your file
 {file}`configuration.nix` to make it contain:
-[]{#module-services-emacs-configuration-nix}
+::: {.example #module-services-emacs-configuration-nix}
+### Custom Emacs in `configuration.nix`
+
 ```
 {
  environment.systemPackages = [
@@ -179,6 +186,7 @@ adding it to the list of system packages (see
   ];
 }
 ```
+:::
 
 In this case, the next {command}`nixos-rebuild switch` will take
 care of adding your {command}`emacs` to the {var}`PATH`
@@ -192,7 +200,9 @@ If you are not on NixOS or want to install this particular Emacs only for
 yourself, you can do so by adding it to your
 {file}`~/.config/nixpkgs/config.nix` (see
 [Nixpkgs manual](https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides)):
-[]{#module-services-emacs-config-nix}
+::: {.example #module-services-emacs-config-nix}
+### Custom Emacs in `~/.config/nixpkgs/config.nix`
+
 ```
 {
   packageOverrides = super: let self = super.pkgs; in {
@@ -200,6 +210,7 @@ yourself, you can do so by adding it to your
   };
 }
 ```
+:::
 
 In this case, the next `nix-env -f '<nixpkgs>' -iA
 myemacs` will take care of adding your emacs to the
@@ -214,7 +225,9 @@ automatically generated {file}`emacs.desktop` (useful if you
 only use {command}`emacsclient`), you can change your file
 {file}`emacs.nix` in this way:
 
-[]{#ex-emacsGtk3Nix}
+::: {.example #ex-emacsGtk3Nix}
+### Custom Emacs build
+
 ```
 { pkgs ? import <nixpkgs> {} }:
 let
@@ -231,8 +244,9 @@ let
   });
 in [...]
 ```
+:::
 
-After building this file as shown in [the example above](#ex-emacsNix), you
+After building this file as shown in [](#ex-emacsNix), you
 will get an GTK 3-based Emacs binary pre-loaded with your favorite packages.
 
 ## Running Emacs as a Service {#module-services-emacs-running}
@@ -327,7 +341,10 @@ This will add the symlink
 
 The Emacs init file should be changed to load the extension packages at
 startup:
-[]{#module-services-emacs-package-initialisation}
+
+::: {.example #module-services-emacs-package-initialisation}
+### Package initialization in `.emacs`
+
 ```
 (require 'package)
 
@@ -337,6 +354,7 @@ startup:
 (setq package-enable-at-startup nil)
 (package-initialize)
 ```
+:::
 
 After the declarative emacs package configuration has been tested,
 previously downloaded packages can be cleaned up by removing
@@ -377,7 +395,9 @@ To install the DocBook 5.0 schemas, either add
 Then customize the variable {var}`rng-schema-locating-files` to
 include {file}`~/.emacs.d/schemas.xml` and put the following
 text into that file:
-[]{#ex-emacs-docbook-xml}
+::: {.example #ex-emacs-docbook-xml}
+### nXML Schema Configuration (`~/.emacs.d/schemas.xml`)
+
 ```xml
 <?xml version="1.0"?>
 <!--
@@ -397,3 +417,4 @@ text into that file:
   -->
 </locatingRules>
 ```
+:::
diff --git a/nixos/modules/services/games/asf.nix b/nixos/modules/services/games/asf.nix
index 7585d56b2d78f..f15d7077d965c 100644
--- a/nixos/modules/services/games/asf.nix
+++ b/nixos/modules/services/games/asf.nix
@@ -245,7 +245,7 @@ in
 
             rm -f www
             ${optionalString cfg.web-ui.enable ''
-              ln -s ${cfg.web-ui.package}/lib/dist www
+              ln -s ${cfg.web-ui.package}/ www
             ''}
           '';
       };
diff --git a/nixos/modules/services/games/minetest-server.nix b/nixos/modules/services/games/minetest-server.nix
index e8c96881673b5..578364ec542bb 100644
--- a/nixos/modules/services/games/minetest-server.nix
+++ b/nixos/modules/services/games/minetest-server.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg   = config.services.minetest-server;
-  flag  = val: name: if val != null then "--${name} ${toString val} " else "";
+  flag  = val: name: optionalString (val != null) "--${name} ${toString val} ";
   flags = [
     (flag cfg.gameId "gameid")
     (flag cfg.world "world")
diff --git a/nixos/modules/services/hardware/asusd.nix b/nixos/modules/services/hardware/asusd.nix
index fba9b059bbbb7..ebbdea26c0514 100644
--- a/nixos/modules/services/hardware/asusd.nix
+++ b/nixos/modules/services/hardware/asusd.nix
@@ -2,8 +2,6 @@
 
 let
   cfg = config.services.asusd;
-  json = pkgs.formats.json { };
-  toml = pkgs.formats.toml { };
 in
 {
   options = {
@@ -19,55 +17,55 @@ in
       };
 
       animeConfig = lib.mkOption {
-        type = json.type;
-        default = { };
+        type = lib.types.nullOr lib.types.str;
+        default = null;
         description = lib.mdDoc ''
-          The content of /etc/asusd/anime.conf.
+          The content of /etc/asusd/anime.ron.
           See https://asus-linux.org/asusctl/#anime-control.
         '';
       };
 
       asusdConfig = lib.mkOption {
-        type = json.type;
-        default = { };
+        type = lib.types.nullOr lib.types.str;
+        default = null;
         description = lib.mdDoc ''
-          The content of /etc/asusd/asusd.conf.
+          The content of /etc/asusd/asusd.ron.
           See https://asus-linux.org/asusctl/.
         '';
       };
 
       auraConfig = lib.mkOption {
-        type = json.type;
-        default = { };
+        type = lib.types.nullOr lib.types.str;
+        default = null;
         description = lib.mdDoc ''
-          The content of /etc/asusd/aura.conf.
+          The content of /etc/asusd/aura.ron.
           See https://asus-linux.org/asusctl/#led-keyboard-control.
         '';
       };
 
       profileConfig = lib.mkOption {
         type = lib.types.nullOr lib.types.str;
-        default = "";
+        default = null;
         description = lib.mdDoc ''
-          The content of /etc/asusd/profile.conf.
+          The content of /etc/asusd/profile.ron.
           See https://asus-linux.org/asusctl/#profiles.
         '';
       };
 
-      ledModesConfig = lib.mkOption {
-        type = lib.types.nullOr toml.type;
-        default = null;
-        description = lib.mdDoc ''
-          The content of /etc/asusd/asusd-ledmodes.toml. Leave `null` to use default settings.
-          See https://asus-linux.org/asusctl/#led-keyboard-control.
+      fanCurvesConfig = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
+      default = null;
+      description = lib.mdDoc ''
+          The content of /etc/asusd/fan_curves.ron.
+          See https://asus-linux.org/asusctl/#fan-curves.
         '';
       };
 
       userLedModesConfig = lib.mkOption {
-        type = lib.types.nullOr toml.type;
+        type = lib.types.nullOr lib.types.str;
         default = null;
         description = lib.mdDoc ''
-          The content of /etc/asusd/asusd-user-ledmodes.toml.
+          The content of /etc/asusd/asusd-user-ledmodes.ron.
           See https://asus-linux.org/asusctl/#led-keyboard-control.
         '';
       };
@@ -79,26 +77,18 @@ in
 
     environment.etc =
       let
-        maybeConfig = name: cfg: lib.mkIf (cfg != { }) {
-          source = json.generate name cfg;
+        maybeConfig = name: cfg: lib.mkIf (cfg != null) {
+          source = pkgs.writeText name cfg;
           mode = "0644";
         };
       in
       {
-        "asusd/anime.conf" = maybeConfig "anime.conf" cfg.animeConfig;
-        "asusd/asusd.conf" = maybeConfig "asusd.conf" cfg.asusdConfig;
-        "asusd/aura.conf" = maybeConfig "aura.conf" cfg.auraConfig;
-        "asusd/profile.conf" = lib.mkIf (cfg.profileConfig != null) {
-          source = pkgs.writeText "profile.conf" cfg.profileConfig;
-          mode = "0644";
-        };
-        "asusd/asusd-ledmodes.toml" = {
-          source =
-            if cfg.ledModesConfig == null
-            then "${pkgs.asusctl}/share/asusd/data/asusd-ledmodes.toml"
-            else toml.generate "asusd-ledmodes.toml" cfg.ledModesConfig;
-          mode = "0644";
-        };
+        "asusd/anime.ron" = maybeConfig "anime.ron" cfg.animeConfig;
+        "asusd/asusd.ron" = maybeConfig "asusd.ron" cfg.asusdConfig;
+        "asusd/aura.ron" = maybeConfig "aura.ron" cfg.auraConfig;
+        "asusd/profile.conf" = maybeConfig "profile.ron" cfg.profileConfig;
+        "asusd/fan_curves.ron" = maybeConfig "fan_curves.ron" cfg.fanCurvesConfig;
+        "asusd/asusd_user_ledmodes.ron" = maybeConfig "asusd_user_ledmodes.ron" cfg.userLedModesConfig;
       };
 
     services.dbus.enable = true;
diff --git a/nixos/modules/services/hardware/auto-cpufreq.nix b/nixos/modules/services/hardware/auto-cpufreq.nix
index df7c01ae54ef0..fd2e03ef12f5d 100644
--- a/nixos/modules/services/hardware/auto-cpufreq.nix
+++ b/nixos/modules/services/hardware/auto-cpufreq.nix
@@ -37,7 +37,7 @@ in {
 
         serviceConfig.ExecStart = [
           ""
-          "${lib.getExe pkgs.auto-cpufreq} --config ${cfgFile}"
+          "${lib.getExe pkgs.auto-cpufreq} --daemon --config ${cfgFile}"
         ];
       };
     };
diff --git a/nixos/modules/services/hardware/keyd.nix b/nixos/modules/services/hardware/keyd.nix
index 64c769405fabc..d17b0e4303ef7 100644
--- a/nixos/modules/services/hardware/keyd.nix
+++ b/nixos/modules/services/hardware/keyd.nix
@@ -76,7 +76,9 @@ in
         ExecStart = "${pkgs.keyd}/bin/keyd";
         Restart = "always";
 
-        DynamicUser = true;
+        # TODO investigate why it doesn't work propeprly with DynamicUser
+        # See issue: https://github.com/NixOS/nixpkgs/issues/226346
+        # DynamicUser = true;
         SupplementaryGroups = [
           config.users.groups.input.name
           config.users.groups.uinput.name
@@ -96,6 +98,7 @@ in
         ProtectHostname = true;
         PrivateUsers = true;
         PrivateMounts = true;
+        PrivateTmp = true;
         RestrictNamespaces = true;
         ProtectKernelLogs = true;
         ProtectKernelModules = true;
@@ -104,7 +107,18 @@ in
         MemoryDenyWriteExecute = true;
         RestrictRealtime = true;
         LockPersonality = true;
-        ProtectProc = "noaccess";
+        ProtectProc = "invisible";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+          "~@resources"
+        ];
+        RestrictAddressFamilies = [ "AF_UNIX" ];
+        RestrictSUIDSGID = true;
+        IPAddressDeny = [ "any" ];
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProcSubset = "pid";
         UMask = "0077";
       };
     };
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index d95261332419d..94406b60b29cf 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -16,16 +16,6 @@ let
   '';
 
 
-  # networkd link files are used early by udev to set up interfaces early.
-  # This must be done in stage 1 to avoid race conditions between udev and
-  # network daemons.
-  # TODO move this into the initrd-network module when it exists
-  initrdLinkUnits = pkgs.runCommand "initrd-link-units" {} ''
-    mkdir -p $out
-    ln -s ${udev}/lib/systemd/network/*.link $out/
-    ${lib.concatMapStringsSep "\n" (file: "ln -s ${file} $out/") (lib.mapAttrsToList (n: v: "${v.unit}/${n}") (lib.filterAttrs (n: _: hasSuffix ".link" n) config.systemd.network.units))}
-  '';
-
   extraUdevRules = pkgs.writeTextFile {
     name = "extra-udev-rules";
     text = cfg.extraRules;
@@ -170,7 +160,7 @@ let
 
       echo "Generating hwdb database..."
       # hwdb --update doesn't return error code even on errors!
-      res="$(${pkgs.buildPackages.udev}/bin/udevadm hwdb --update --root=$(pwd) 2>&1)"
+      res="$(${pkgs.buildPackages.systemd}/bin/systemd-hwdb --root=$(pwd) update 2>&1)"
       echo "$res"
       [ -z "$(echo "$res" | egrep '^Error')" ]
       mv etc/udev/hwdb.bin $out
@@ -398,7 +388,6 @@ in
         systemd = config.boot.initrd.systemd.package;
         binPackages = config.boot.initrd.services.udev.binPackages ++ [ config.boot.initrd.systemd.contents."/bin".source ];
       };
-      "/etc/systemd/network".source = initrdLinkUnits;
     };
     # Insert initrd rules
     boot.initrd.services.udev.packages = [
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index ac905a274af28..abe0b93e412cb 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -461,6 +461,7 @@ in {
           "mopeka"
           "oralb"
           "qingping"
+          "rapt_ble"
           "ruuvi_gateway"
           "ruuvitag_ble"
           "sensirion_ble"
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index 70c3ca50888c7..1eb51c50ff79c 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -37,8 +37,8 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.graylog;
-        defaultText = literalExpression "pkgs.graylog";
+        default = if versionOlder config.system.stateVersion "23.05" then pkgs.graylog-3_3 else pkgs.graylog-5_0;
+        defaultText = literalExpression (if versionOlder config.system.stateVersion "23.05" then "pkgs.graylog-3_3" else "pkgs.graylog-5_0");
         description = lib.mdDoc "Graylog package to use.";
       };
 
diff --git a/nixos/modules/services/logging/logrotate.nix b/nixos/modules/services/logging/logrotate.nix
index b056f96c3630b..342ac5ec6e049 100644
--- a/nixos/modules/services/logging/logrotate.nix
+++ b/nixos/modules/services/logging/logrotate.nix
@@ -83,9 +83,8 @@ let
   };
 
   mailOption =
-    if foldr (n: a: a || (n.mail or false) != false) false (attrValues cfg.settings)
-    then "--mail=${pkgs.mailutils}/bin/mail"
-    else "";
+    optionalString (foldr (n: a: a || (n.mail or false) != false) false (attrValues cfg.settings))
+    "--mail=${pkgs.mailutils}/bin/mail";
 in
 {
   imports = [
diff --git a/nixos/modules/services/logging/syslogd.nix b/nixos/modules/services/logging/syslogd.nix
index 43969402588db..553973e255f7e 100644
--- a/nixos/modules/services/logging/syslogd.nix
+++ b/nixos/modules/services/logging/syslogd.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.syslogd;
 
   syslogConf = pkgs.writeText "syslog.conf" ''
-    ${if (cfg.tty != "") then "kern.warning;*.err;authpriv.none /dev/${cfg.tty}" else ""}
+    ${optionalString (cfg.tty != "") "kern.warning;*.err;authpriv.none /dev/${cfg.tty}"}
     ${cfg.defaultConfig}
     ${cfg.extraConfig}
   '';
diff --git a/nixos/modules/services/logging/vector.nix b/nixos/modules/services/logging/vector.nix
index 1803ea85e49c8..f2edeabfc06f0 100644
--- a/nixos/modules/services/logging/vector.nix
+++ b/nixos/modules/services/logging/vector.nix
@@ -8,6 +8,8 @@ in
   options.services.vector = {
     enable = mkEnableOption (lib.mdDoc "Vector");
 
+    package = mkPackageOptionMD pkgs "vector" { };
+
     journaldAccess = mkOption {
       type = types.bool;
       default = false;
@@ -26,13 +28,9 @@ in
   };
 
   config = mkIf cfg.enable {
+    # for cli usage
+    environment.systemPackages = [ pkgs.vector ];
 
-    users.groups.vector = { };
-    users.users.vector = {
-      description = "Vector service user";
-      group = "vector";
-      isSystemUser = true;
-    };
     systemd.services.vector = {
       description = "Vector event and log aggregator";
       wantedBy = [ "multi-user.target" ];
@@ -51,9 +49,8 @@ in
             '';
         in
         {
-          ExecStart = "${pkgs.vector}/bin/vector --config ${validateConfig conf}";
-          User = "vector";
-          Group = "vector";
+          ExecStart = "${getExe cfg.package} --config ${validateConfig conf}";
+          DynamicUser = true;
           Restart = "no";
           StateDirectory = "vector";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixos/modules/services/mail/maddy.nix b/nixos/modules/services/mail/maddy.nix
index d0b525bcb0027..701d57f18e0c8 100644
--- a/nixos/modules/services/mail/maddy.nix
+++ b/nixos/modules/services/mail/maddy.nix
@@ -13,8 +13,6 @@ let
     # configuration here https://github.com/foxcpp/maddy/blob/master/maddy.conf
     # Do not use this in production!
 
-    tls off
-
     auth.pass_table local_authdb {
       table sql_table {
         driver sqlite3
@@ -35,6 +33,7 @@ let
       }
       optional_step file /etc/maddy/aliases
     }
+
     msgpipeline local_routing {
       destination postmaster $(local_domains) {
         modify {
@@ -207,7 +206,7 @@ in {
           Server configuration, see
           [https://maddy.email](https://maddy.email) for
           more information. The default configuration of this module will setup
-          minimal maddy instance for mail transfer without TLS encryption.
+          minimal Maddy instance for mail transfer without TLS encryption.
 
           ::: {.note}
           This should not be used in a production environment.
@@ -215,6 +214,76 @@ in {
         '';
       };
 
+      tls = {
+        loader = mkOption {
+          type = with types; nullOr (enum [ "off" "file" "acme" ]);
+          default = "off";
+          description = lib.mdDoc ''
+            TLS certificates are obtained by modules called "certificate
+            loaders".
+
+            The `file` loader module reads certificates from files specified by
+            the `certificates` option.
+
+            Alternatively the `acme` module can be used to automatically obtain
+            certificates using the ACME protocol.
+
+            Module configuration is done via the `tls.extraConfig` option.
+
+            Secrets such as API keys or passwords should not be supplied in
+            plaintext. Instead the `secrets` option can be used to read secrets
+            at runtime as environment variables. Secrets can be referenced with
+            `{env:VAR}`.
+          '';
+        };
+
+        certificates = mkOption {
+          type = with types; listOf (submodule {
+            options = {
+              keyPath = mkOption {
+                type = types.path;
+                example = "/etc/ssl/mx1.example.org.key";
+                description = lib.mdDoc ''
+                  Path to the private key used for TLS.
+                '';
+              };
+              certPath = mkOption {
+                type = types.path;
+                example = "/etc/ssl/mx1.example.org.crt";
+                description = lib.mdDoc ''
+                  Path to the certificate used for TLS.
+                '';
+              };
+            };
+          });
+          default = [];
+          example = lib.literalExpression ''
+            [{
+              keyPath = "/etc/ssl/mx1.example.org.key";
+              certPath = "/etc/ssl/mx1.example.org.crt";
+            }]
+          '';
+          description = lib.mdDoc ''
+            A list of attribute sets containing paths to TLS certificates and
+            keys. Maddy will use SNI if multiple pairs are selected.
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = with types; nullOr lines;
+          description = lib.mdDoc ''
+            Arguments for the specified certificate loader.
+
+            In case the `tls` loader is set, the defaults are considered secure
+            and there is no need to change anything in most cases.
+            For available options see [upstream manual](https://maddy.email/reference/tls/).
+
+            For ACME configuration, see [following page](https://maddy.email/reference/tls-acme).
+          '';
+          default = "";
+        };
+      };
+
       openFirewall = mkOption {
         type = types.bool;
         default = false;
@@ -224,7 +293,7 @@ in {
       };
 
       ensureAccounts = mkOption {
-        type = types.listOf types.str;
+        type = with types; listOf str;
         default = [];
         description = lib.mdDoc ''
           List of IMAP accounts which get automatically created. Note that for
@@ -265,11 +334,42 @@ in {
         });
       };
 
+      secrets = lib.mkOption {
+        type = lib.types.path;
+        description = lib.mdDoc ''
+          A file containing the various secrets. Should be in the format
+          expected by systemd's `EnvironmentFile` directory. Secrets can be
+          referenced in the format `{env:VAR}`.
+        '';
+      };
+
     };
   };
 
   config = mkIf cfg.enable {
 
+    assertions = [
+      {
+        assertion = cfg.tls.loader == "file" -> cfg.tls.certificates != [];
+        message = ''
+          If Maddy is configured to use TLS, tls.certificates with attribute sets
+          of certPath and keyPath must be provided.
+          Read more about obtaining TLS certificates here:
+          https://maddy.email/tutorials/setting-up/#tls-certificates
+        '';
+      }
+      {
+        assertion = cfg.tls.loader == "acme" -> cfg.tls.extraConfig != "";
+        message = ''
+          If Maddy is configured to obtain TLS certificates using the ACME
+          loader, extra configuration options must be supplied via
+          tls.extraConfig option.
+          See upstream documentation for more details:
+          https://maddy.email/reference/tls-acme
+        '';
+      }
+    ];
+
     systemd = {
 
       packages = [ pkgs.maddy ];
@@ -279,6 +379,7 @@ in {
             User = cfg.user;
             Group = cfg.group;
             StateDirectory = [ "maddy" ];
+            EnvironmentFile = lib.mkIf (cfg.secrets != null) "${cfg.secrets}";
           };
           restartTriggers = [ config.environment.etc."maddy/maddy.conf".source ];
           wantedBy = [ "multi-user.target" ];
@@ -318,6 +419,23 @@ in {
         $(primary_domain) = ${cfg.primaryDomain}
         $(local_domains) = ${toString cfg.localDomains}
         hostname ${cfg.hostname}
+
+        ${if (cfg.tls.loader == "file") then ''
+          tls file ${concatStringsSep " " (
+            map (x: x.certPath + " " + x.keyPath
+          ) cfg.tls.certificates)} ${optionalString (cfg.tls.extraConfig != "") ''
+            { ${cfg.tls.extraConfig} }
+          ''}
+        '' else if (cfg.tls.loader == "acme") then ''
+          tls {
+            loader acme {
+              ${cfg.tls.extraConfig}
+            }
+          }
+        '' else if (cfg.tls.loader == "off") then ''
+          tls off
+        '' else ""}
+
         ${cfg.config}
       '';
     };
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index 852340c05aa7a..23c47aaca7e23 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -234,7 +234,7 @@ let
 
   headerChecks = concatStringsSep "\n" (map (x: "${x.pattern} ${x.action}") cfg.headerChecks) + cfg.extraHeaderChecks;
 
-  aliases = let separator = if cfg.aliasMapType == "hash" then ":" else ""; in
+  aliases = let separator = optionalString (cfg.aliasMapType == "hash") ":"; in
     optionalString (cfg.postmasterAlias != "") ''
       postmaster${separator} ${cfg.postmasterAlias}
     ''
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 3aaec145930db..b9cf526b0bbe2 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -70,7 +70,12 @@ in
       };
       passwordFile = mkOption {
         type = types.str;
-        description = lib.mdDoc "Password file for the postgresql connection. Must be readable by user `nginx`. Ignored if `database.host` is set to `localhost`, as peer authentication will be used.";
+        description = lib.mdDoc ''
+          Password file for the postgresql connection.
+          Must be formated according to PostgreSQL .pgpass standard (see https://www.postgresql.org/docs/current/libpq-pgpass.html)
+          but only one line, no comments and readable by user `nginx`.
+          Ignored if `database.host` is set to `localhost`, as peer authentication will be used.
+        '';
       };
       dbname = mkOption {
         type = types.str;
@@ -123,7 +128,13 @@ in
     environment.etc."roundcube/config.inc.php".text = ''
       <?php
 
-      ${lib.optionalString (!localDB) "$password = file_get_contents('${cfg.database.passwordFile}');"}
+      ${lib.optionalString (!localDB) ''
+        $password = file('${cfg.database.passwordFile}')[0];
+        $password = preg_split('~\\\\.(*SKIP)(*FAIL)|\:~s', $password);
+        $password = end($password);
+        $password = str_replace("\\:", ":", $password);
+        $password = str_replace("\\\\", "\\", $password);
+      ''}
 
       $config = array();
       $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}';
@@ -223,6 +234,7 @@ in
         path = [ config.services.postgresql.package ];
       })
       {
+        after = [ "network-online.target" ];
         wantedBy = [ "multi-user.target" ];
         script = let
           psql = "${lib.optionalString (!localDB) "PGPASSFILE=${cfg.database.passwordFile}"} ${pkgs.postgresql}/bin/psql ${lib.optionalString (!localDB) "-h ${cfg.database.host} -U ${cfg.database.username} "} ${cfg.database.dbname}";
diff --git a/nixos/modules/services/matrix/mjolnir.nix b/nixos/modules/services/matrix/mjolnir.nix
index b6a3e5e8c7308..0824be663340b 100644
--- a/nixos/modules/services/matrix/mjolnir.nix
+++ b/nixos/modules/services/matrix/mjolnir.nix
@@ -30,7 +30,7 @@ let
 
   # these config files will be merged one after the other to build the final config
   configFiles = [
-    "${pkgs.mjolnir}/share/mjolnir/config/default.yaml"
+    "${pkgs.mjolnir}/libexec/mjolnir/deps/mjolnir/config/default.yaml"
     moduleConfigFile
   ];
 
@@ -200,7 +200,7 @@ in
       wantedBy = [ "multi-user.target" ];
 
       serviceConfig = {
-        ExecStart = ''${pkgs.mjolnir}/bin/mjolnir'';
+        ExecStart = ''${pkgs.mjolnir}/bin/mjolnir --mjolnir-config ./config/default.yaml'';
         ExecStartPre = [ generateConfig ];
         WorkingDirectory = cfg.dataPath;
         StateDirectory = "mjolnir";
diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix
index 3343e94778a2b..17a7cca917f24 100644
--- a/nixos/modules/services/misc/etcd.nix
+++ b/nixos/modules/services/misc/etcd.nix
@@ -167,10 +167,11 @@ in {
         ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
         ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
         ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
+        ETCD_PEER_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
         ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile;
         ETCD_PEER_CERT_FILE = cfg.peerCertFile;
         ETCD_PEER_KEY_FILE = cfg.peerKeyFile;
-        ETCD_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
+        ETCD_CLIENT_CERT_AUTH = toString cfg.clientCertAuth;
         ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile;
         ETCD_CERT_FILE = cfg.certFile;
         ETCD_KEY_FILE = cfg.keyFile;
diff --git a/nixos/modules/services/misc/fstrim.nix b/nixos/modules/services/misc/fstrim.nix
index 36b5f9c8cca13..55fb24e292723 100644
--- a/nixos/modules/services/misc/fstrim.nix
+++ b/nixos/modules/services/misc/fstrim.nix
@@ -34,7 +34,7 @@ in {
 
     systemd.timers.fstrim = {
       timerConfig = {
-        OnCalendar = cfg.interval;
+        OnCalendar = [ "" cfg.interval ];
       };
       wantedBy = [ "timers.target" ];
     };
diff --git a/nixos/modules/services/misc/gammu-smsd.nix b/nixos/modules/services/misc/gammu-smsd.nix
index 83f4efe695a27..eff725f5a8685 100644
--- a/nixos/modules/services/misc/gammu-smsd.nix
+++ b/nixos/modules/services/misc/gammu-smsd.nix
@@ -10,7 +10,7 @@ let
     Connection = ${cfg.device.connection}
     SynchronizeTime = ${if cfg.device.synchronizeTime then "yes" else "no"}
     LogFormat = ${cfg.log.format}
-    ${if (cfg.device.pin != null) then "PIN = ${cfg.device.pin}" else ""}
+    ${optionalString (cfg.device.pin != null) "PIN = ${cfg.device.pin}"}
     ${cfg.extraConfig.gammu}
 
 
@@ -33,10 +33,10 @@ let
     ${optionalString (cfg.backend.service == "sql" && cfg.backend.sql.driver == "native_pgsql") (
       with cfg.backend; ''
         Driver = ${sql.driver}
-        ${if (sql.database!= null) then "Database = ${sql.database}" else ""}
-        ${if (sql.host != null) then "Host = ${sql.host}" else ""}
-        ${if (sql.user != null) then "User = ${sql.user}" else ""}
-        ${if (sql.password != null) then "Password = ${sql.password}" else ""}
+        ${optionalString (sql.database!= null) "Database = ${sql.database}"}
+        ${optionalString (sql.host != null) "Host = ${sql.host}"}
+        ${optionalString (sql.user != null) "User = ${sql.user}"}
+        ${optionalString (sql.password != null) "Password = ${sql.password}"}
       '')}
 
     ${cfg.extraConfig.smsd}
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index e019e431a1890..0c414c2466bf4 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -26,9 +26,18 @@ in
   imports = [
     (mkRenamedOptionModule [ "services" "gitea" "cookieSecure" ] [ "services" "gitea" "settings" "session" "COOKIE_SECURE" ])
     (mkRenamedOptionModule [ "services" "gitea" "disableRegistration" ] [ "services" "gitea" "settings" "service" "DISABLE_REGISTRATION" ])
+    (mkRenamedOptionModule [ "services" "gitea" "domain" ] [ "services" "gitea" "settings" "server" "DOMAIN" ])
+    (mkRenamedOptionModule [ "services" "gitea" "httpAddress" ] [ "services" "gitea" "settings" "server" "HTTP_ADDR" ])
+    (mkRenamedOptionModule [ "services" "gitea" "httpPort" ] [ "services" "gitea" "settings" "server" "HTTP_PORT" ])
     (mkRenamedOptionModule [ "services" "gitea" "log" "level" ] [ "services" "gitea" "settings" "log" "LEVEL" ])
     (mkRenamedOptionModule [ "services" "gitea" "log" "rootPath" ] [ "services" "gitea" "settings" "log" "ROOT_PATH" ])
+    (mkRenamedOptionModule [ "services" "gitea" "rootUrl" ] [ "services" "gitea" "settings" "server" "ROOT_URL" ])
     (mkRenamedOptionModule [ "services" "gitea" "ssh" "clonePort" ] [ "services" "gitea" "settings" "server" "SSH_PORT" ])
+    (mkRenamedOptionModule [ "services" "gitea" "staticRootPath" ] [ "services" "gitea" "settings" "server" "STATIC_ROOT_PATH" ])
+
+    (mkChangedOptionModule [ "services" "gitea" "enableUnixSocket" ] [ "services" "gitea" "settings" "server" "PROTOCOL" ] (
+      config: if config.services.gitea.enableUnixSocket then "http+unix" else "http"
+    ))
 
     (mkRemovedOptionModule [ "services" "gitea" "ssh" "enable" ] "services.gitea.ssh.enable has been migrated into freeform setting services.gitea.settings.server.DISABLE_SSH. Keep in mind that the setting is inverted")
   ];
@@ -57,7 +66,14 @@ in
       stateDir = mkOption {
         default = "/var/lib/gitea";
         type = types.str;
-        description = lib.mdDoc "gitea data directory.";
+        description = lib.mdDoc "Gitea data directory.";
+      };
+
+      customDir = mkOption {
+        default = "${cfg.stateDir}/custom";
+        defaultText = literalExpression ''"''${config.${opt.stateDir}}/custom"'';
+        type = types.str;
+        description = lib.mdDoc "Gitea custom directory. Used for config, custom templates and other options.";
       };
 
       user = mkOption {
@@ -66,6 +82,12 @@ in
         description = lib.mdDoc "User account under which gitea runs.";
       };
 
+      group = mkOption {
+        type = types.str;
+        default = "gitea";
+        description = lib.mdDoc "Group under which gitea runs.";
+      };
+
       database = {
         type = mkOption {
           type = types.enum [ "sqlite3" "mysql" "postgres" ];
@@ -216,44 +238,6 @@ in
         description = lib.mdDoc "Path to the git repositories.";
       };
 
-      domain = mkOption {
-        type = types.str;
-        default = "localhost";
-        description = lib.mdDoc "Domain name of your server.";
-      };
-
-      rootUrl = mkOption {
-        type = types.str;
-        default = "http://localhost:3000/";
-        description = lib.mdDoc "Full public URL of gitea server.";
-      };
-
-      httpAddress = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
-        description = lib.mdDoc "HTTP listen address.";
-      };
-
-      httpPort = mkOption {
-        type = types.port;
-        default = 3000;
-        description = lib.mdDoc "HTTP listen port.";
-      };
-
-      enableUnixSocket = mkOption {
-        type = types.bool;
-        default = false;
-        description = lib.mdDoc "Configure Gitea to listen on a unix socket instead of the default TCP port.";
-      };
-
-      staticRootPath = mkOption {
-        type = types.either types.str types.path;
-        default = cfg.package.data;
-        defaultText = literalExpression "package.data";
-        example = "/var/lib/gitea/data";
-        description = lib.mdDoc "Upper level of template and static files path.";
-      };
-
       mailerPasswordFile = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -285,7 +269,7 @@ in
             };
           }
         '';
-        type = with types; submodule {
+        type = types.submodule {
           freeformType = format.type;
           options = {
             log = {
@@ -303,6 +287,46 @@ in
             };
 
             server = {
+              PROTOCOL = mkOption {
+                type = types.enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ];
+                default = "http";
+                description = lib.mdDoc ''Listen protocol. `+unix` means "over unix", not "in addition to."'';
+              };
+
+              HTTP_ADDR = mkOption {
+                type = types.either types.str types.path;
+                default = if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/gitea/gitea.sock" else "0.0.0.0";
+                defaultText = literalExpression ''if lib.hasSuffix "+unix" cfg.settings.server.PROTOCOL then "/run/gitea/gitea.sock" else "0.0.0.0"'';
+                description = lib.mdDoc "Listen address. Must be a path when using a unix socket.";
+              };
+
+              HTTP_PORT = mkOption {
+                type = types.port;
+                default = 3000;
+                description = lib.mdDoc "Listen port. Ignored when using a unix socket.";
+              };
+
+              DOMAIN = mkOption {
+                type = types.str;
+                default = "localhost";
+                description = lib.mdDoc "Domain name of your server.";
+              };
+
+              ROOT_URL = mkOption {
+                type = types.str;
+                default = "http://${cfg.settings.server.DOMAIN}:${toString cfg.settings.server.HTTP_PORT}/";
+                defaultText = literalExpression ''"http://''${config.services.gitea.settings.server.DOMAIN}:''${toString config.services.gitea.settings.server.HTTP_PORT}/"'';
+                description = lib.mdDoc "Full public URL of gitea server.";
+              };
+
+              STATIC_ROOT_PATH = mkOption {
+                type = types.either types.str types.path;
+                default = cfg.package.data;
+                defaultText = literalExpression "config.${opt.package}.data";
+                example = "/var/lib/gitea/data";
+                description = lib.mdDoc "Upper level of template and static files path.";
+              };
+
               DISABLE_SSH = mkOption {
                 type = types.bool;
                 default = false;
@@ -359,7 +383,7 @@ in
 
   config = mkIf cfg.enable {
     assertions = [
-      { assertion = cfg.database.createDatabase -> cfg.database.user == cfg.user;
+      { assertion = cfg.database.createDatabase -> useSqlite || cfg.database.user == cfg.user;
         message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
       }
     ];
@@ -389,26 +413,10 @@ in
         ROOT = cfg.repositoryRoot;
       };
 
-      server = mkMerge [
-        {
-          DOMAIN = cfg.domain;
-          STATIC_ROOT_PATH = toString cfg.staticRootPath;
-          LFS_JWT_SECRET = "#lfsjwtsecret#";
-          ROOT_URL = cfg.rootUrl;
-        }
-        (mkIf cfg.enableUnixSocket {
-          PROTOCOL = "http+unix";
-          HTTP_ADDR = "/run/gitea/gitea.sock";
-        })
-        (mkIf (!cfg.enableUnixSocket) {
-          HTTP_ADDR = cfg.httpAddress;
-          HTTP_PORT = cfg.httpPort;
-        })
-        (mkIf cfg.lfs.enable {
-          LFS_START_SERVER = true;
-        })
-
-      ];
+      server = mkIf cfg.lfs.enable {
+        LFS_START_SERVER = true;
+        LFS_JWT_SECRET = "#lfsjwtsecret#";
+      };
 
       session = {
         COOKIE_NAME = lib.mkDefault "session";
@@ -428,7 +436,7 @@ in
         JWT_SECRET = "#oauth2jwtsecret#";
       };
 
-      lfs = mkIf (cfg.lfs.enable) {
+      lfs = mkIf cfg.lfs.enable {
         PATH = cfg.lfs.contentDir;
       };
     };
@@ -457,33 +465,35 @@ in
     };
 
     systemd.tmpfiles.rules = [
-      "d '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.dump.backupDir}' 0750 ${cfg.user} gitea - -"
-      "Z '${cfg.dump.backupDir}' - ${cfg.user} gitea - -"
-      "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} gitea - -"
-      "Z '${cfg.lfs.contentDir}' - ${cfg.user} gitea - -"
-      "d '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.repositoryRoot}' 0750 ${cfg.user} gitea - -"
-      "Z '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/data' 0750 ${cfg.user} gitea - -"
-      "d '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/conf' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/custom' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/custom/conf' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/data' 0750 ${cfg.user} gitea - -"
-      "z '${cfg.stateDir}/log' 0750 ${cfg.user} gitea - -"
-      "Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
+      "d '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.dump.backupDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.dump.backupDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.repositoryRoot}' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.repositoryRoot}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/.ssh' 0700 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.customDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.customDir}/conf' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/data' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
 
       # If we have a folder or symlink with gitea locales, remove it
       # And symlink the current gitea locales in place
       "L+ '${cfg.stateDir}/conf/locale' - - - - ${cfg.package.out}/locale"
+
+    ] ++ lib.optionals cfg.lfs.enable [
+      "d '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "z '${cfg.lfs.contentDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.lfs.contentDir}' - ${cfg.user} ${cfg.group} - -"
     ];
 
     systemd.services.gitea = {
@@ -500,47 +510,52 @@ in
       # lfs_jwt_secret.
       # We have to consider this to stay compatible with older installations.
       preStart = let
-        runConfig = "${cfg.stateDir}/custom/conf/app.ini";
-        secretKey = "${cfg.stateDir}/custom/conf/secret_key";
-        oauth2JwtSecret = "${cfg.stateDir}/custom/conf/oauth2_jwt_secret";
-        oldLfsJwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret"; # old file for LFS_JWT_SECRET
-        lfsJwtSecret = "${cfg.stateDir}/custom/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
-        internalToken = "${cfg.stateDir}/custom/conf/internal_token";
+        runConfig = "${cfg.customDir}/conf/app.ini";
+        secretKey = "${cfg.customDir}/conf/secret_key";
+        oauth2JwtSecret = "${cfg.customDir}/conf/oauth2_jwt_secret";
+        oldLfsJwtSecret = "${cfg.customDir}/conf/jwt_secret"; # old file for LFS_JWT_SECRET
+        lfsJwtSecret = "${cfg.customDir}/conf/lfs_jwt_secret"; # new file for LFS_JWT_SECRET
+        internalToken = "${cfg.customDir}/conf/internal_token";
         replaceSecretBin = "${pkgs.replace-secret}/bin/replace-secret";
       in ''
-        # copy custom configuration and generate a random secret key if needed
+        # copy custom configuration and generate random secrets if needed
         ${optionalString (!cfg.useWizard) ''
           function gitea_setup {
-            cp -f ${configFile} ${runConfig}
+            cp -f '${configFile}' '${runConfig}'
 
-            if [ ! -s ${secretKey} ]; then
-                ${exe} generate secret SECRET_KEY > ${secretKey}
+            if [ ! -s '${secretKey}' ]; then
+                ${exe} generate secret SECRET_KEY > '${secretKey}'
             fi
 
             # Migrate LFS_JWT_SECRET filename
-            if [[ -s ${oldLfsJwtSecret} && ! -s ${lfsJwtSecret} ]]; then
-                mv ${oldLfsJwtSecret} ${lfsJwtSecret}
+            if [[ -s '${oldLfsJwtSecret}' && ! -s '${lfsJwtSecret}' ]]; then
+                mv '${oldLfsJwtSecret}' '${lfsJwtSecret}'
             fi
 
-            if [ ! -s ${oauth2JwtSecret} ]; then
-                ${exe} generate secret JWT_SECRET > ${oauth2JwtSecret}
+            if [ ! -s '${oauth2JwtSecret}' ]; then
+                ${exe} generate secret JWT_SECRET > '${oauth2JwtSecret}'
             fi
 
-            if [ ! -s ${lfsJwtSecret} ]; then
-                ${exe} generate secret LFS_JWT_SECRET > ${lfsJwtSecret}
+            ${lib.optionalString cfg.lfs.enable ''
+            if [ ! -s '${lfsJwtSecret}' ]; then
+                ${exe} generate secret LFS_JWT_SECRET > '${lfsJwtSecret}'
             fi
+            ''}
 
-            if [ ! -s ${internalToken} ]; then
-                ${exe} generate secret INTERNAL_TOKEN > ${internalToken}
+            if [ ! -s '${internalToken}' ]; then
+                ${exe} generate secret INTERNAL_TOKEN > '${internalToken}'
             fi
 
             chmod u+w '${runConfig}'
             ${replaceSecretBin} '#secretkey#' '${secretKey}' '${runConfig}'
             ${replaceSecretBin} '#dbpass#' '${cfg.database.passwordFile}' '${runConfig}'
             ${replaceSecretBin} '#oauth2jwtsecret#' '${oauth2JwtSecret}' '${runConfig}'
-            ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
             ${replaceSecretBin} '#internaltoken#' '${internalToken}' '${runConfig}'
 
+            ${lib.optionalString cfg.lfs.enable ''
+              ${replaceSecretBin} '#lfsjwtsecret#' '${lfsJwtSecret}' '${runConfig}'
+            ''}
+
             ${lib.optionalString (cfg.mailerPasswordFile != null) ''
               ${replaceSecretBin} '#mailerpass#' '${cfg.mailerPasswordFile}' '${runConfig}'
             ''}
@@ -565,7 +580,7 @@ in
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
-        Group = "gitea";
+        Group = cfg.group;
         WorkingDirectory = cfg.stateDir;
         ExecStart = "${exe} web --pid /run/gitea/gitea.pid";
         Restart = "always";
@@ -573,7 +588,7 @@ in
         RuntimeDirectory = "gitea";
         RuntimeDirectoryMode = "0755";
         # Access write directories
-        ReadWritePaths = [ cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
+        ReadWritePaths = [ cfg.customDir cfg.dump.backupDir cfg.repositoryRoot cfg.stateDir cfg.lfs.contentDir ];
         UMask = "0027";
         # Capabilities
         CapabilityBoundingSet = "";
@@ -606,6 +621,7 @@ in
         USER = cfg.user;
         HOME = cfg.stateDir;
         GITEA_WORK_DIR = cfg.stateDir;
+        GITEA_CUSTOM = cfg.customDir;
       };
     };
 
@@ -614,12 +630,14 @@ in
         description = "Gitea Service";
         home = cfg.stateDir;
         useDefaultShell = true;
-        group = "gitea";
+        group = cfg.group;
         isSystemUser = true;
       };
     };
 
-    users.groups.gitea = {};
+    users.groups = mkIf (cfg.group == "gitea") {
+      gitea = {};
+    };
 
     warnings =
       optional (cfg.database.password != "") "config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead." ++
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index d278b571a6410..12c67c5f5a1e7 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -1215,7 +1215,7 @@ in {
       enableDelete = true; # This must be true, otherwise GitLab won't manage it correctly
       extraConfig = {
         auth.token = {
-          realm = "http${if cfg.https == true then "s" else ""}://${cfg.host}/jwt/auth";
+          realm = "http${optionalString (cfg.https == true) "s"}://${cfg.host}/jwt/auth";
           service = cfg.registry.serviceName;
           issuer = cfg.registry.issuer;
           rootcertbundle = cfg.registry.certFile;
diff --git a/nixos/modules/services/misc/klipper.nix b/nixos/modules/services/misc/klipper.nix
index 9f8539980aaab..ad881d4462a3c 100644
--- a/nixos/modules/services/misc/klipper.nix
+++ b/nixos/modules/services/misc/klipper.nix
@@ -23,6 +23,16 @@ in
         description = lib.mdDoc "The Klipper package.";
       };
 
+      logFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/lib/klipper/klipper.log";
+        description = lib.mdDoc ''
+          Path of the file Klipper should log to.
+          If `null`, it logs to stdout, which is not recommended by upstream.
+        '';
+      };
+
       inputTTY = mkOption {
         type = types.path;
         default = "/run/klipper/tty";
@@ -151,7 +161,9 @@ in
     systemd.services.klipper =
       let
         klippyArgs = "--input-tty=${cfg.inputTTY}"
-          + optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}";
+          + optionalString (cfg.apiSocket != null) " --api-server=${cfg.apiSocket}"
+          + optionalString (cfg.logFile != null) " --logfile=${cfg.logFile}"
+        ;
         printerConfigPath =
           if cfg.mutableConfig
           then cfg.mutableConfigFolder + "/printer.cfg"
@@ -177,7 +189,7 @@ in
         '';
 
         serviceConfig = {
-          ExecStart = "${cfg.package}/lib/klipper/klippy.py ${klippyArgs} ${printerConfigPath}";
+          ExecStart = "${cfg.package}/bin/klippy ${klippyArgs} ${printerConfigPath}";
           RuntimeDirectory = "klipper";
           StateDirectory = "klipper";
           SupplementaryGroups = [ "dialout" ];
diff --git a/nixos/modules/services/misc/mbpfan.nix b/nixos/modules/services/misc/mbpfan.nix
index 1a6b54854d1cd..e75c352541438 100644
--- a/nixos/modules/services/misc/mbpfan.nix
+++ b/nixos/modules/services/misc/mbpfan.nix
@@ -3,7 +3,7 @@ with lib;
 
 let
   cfg = config.services.mbpfan;
-  verbose = if cfg.verbose then "v" else "";
+  verbose = optionalString cfg.verbose "v";
   settingsFormat = pkgs.formats.ini {};
   settingsFile = settingsFormat.generate "mbpfan.ini" cfg.settings;
 
diff --git a/nixos/modules/services/misc/moonraker.nix b/nixos/modules/services/misc/moonraker.nix
index 53638ded29634..7e306d718e082 100644
--- a/nixos/modules/services/misc/moonraker.nix
+++ b/nixos/modules/services/misc/moonraker.nix
@@ -72,7 +72,7 @@ in {
         example = {
           authorization = {
             trusted_clients = [ "10.0.0.0/24" ];
-            cors_domains = [ "https://app.fluidd.xyz" ];
+            cors_domains = [ "https://app.fluidd.xyz" "https://my.mainsail.xyz" ];
           };
         };
         description = lib.mdDoc ''
diff --git a/nixos/modules/services/misc/pufferpanel.nix b/nixos/modules/services/misc/pufferpanel.nix
new file mode 100644
index 0000000000000..78ec356469076
--- /dev/null
+++ b/nixos/modules/services/misc/pufferpanel.nix
@@ -0,0 +1,176 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.pufferpanel;
+in
+{
+  options.services.pufferpanel = {
+    enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Whether to enable PufferPanel game management server.
+
+        Note that [PufferPanel templates] and binaries downloaded by PufferPanel
+        expect [FHS environment]. It is possible to set {option}`package` option
+        to use PufferPanel wrapper with FHS environment. For example, to use
+        `Download Game from Steam` and `Download Java` template operations:
+        ```Nix
+        { lib, pkgs, ... }: {
+          services.pufferpanel = {
+            enable = true;
+            extraPackages = with pkgs; [ bash curl gawk gnutar gzip ];
+            package = pkgs.buildFHSUserEnv {
+              name = "pufferpanel-fhs";
+              runScript = lib.getExe pkgs.pufferpanel;
+              targetPkgs = pkgs': with pkgs'; [ icu openssl zlib ];
+            };
+          };
+        }
+        ```
+
+        [PufferPanel templates]: https://github.com/PufferPanel/templates
+        [FHS environment]: https://wikipedia.org/wiki/Filesystem_Hierarchy_Standard
+      '';
+    };
+
+    package = lib.mkPackageOptionMD pkgs "pufferpanel" { };
+
+    extraGroups = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ ];
+      example = [ "podman" ];
+      description = lib.mdDoc ''
+        Additional groups for the systemd service.
+      '';
+    };
+
+    extraPackages = lib.mkOption {
+      type = lib.types.listOf lib.types.package;
+      default = [ ];
+      example = lib.literalExpression "[ pkgs.jre ]";
+      description = lib.mdDoc ''
+        Packages to add to the PATH environment variable. Both the {file}`bin`
+        and {file}`sbin` subdirectories of each package are added.
+      '';
+    };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      default = { };
+      example = lib.literalExpression ''
+        {
+          PUFFER_WEB_HOST = ":8080";
+          PUFFER_DAEMON_SFTP_HOST = ":5657";
+          PUFFER_DAEMON_CONSOLE_BUFFER = "1000";
+          PUFFER_DAEMON_CONSOLE_FORWARD = "true";
+          PUFFER_PANEL_REGISTRATIONENABLED = "false";
+        }
+      '';
+      description = lib.mdDoc ''
+        Environment variables to set for the service. Secrets should be
+        specified using {option}`environmentFile`.
+
+        Refer to the [PufferPanel source code][] for the list of available
+        configuration options. Variable name is an upper-cased configuration
+        entry name with underscores instead of dots, prefixed with `PUFFER_`.
+        For example, `panel.settings.companyName` entry can be set using
+        {env}`PUFFER_PANEL_SETTINGS_COMPANYNAME`.
+
+        When running with panel enabled (configured with `PUFFER_PANEL_ENABLE`
+        environment variable), it is recommended disable registration using
+        `PUFFER_PANEL_REGISTRATIONENABLED` environment variable (registration is
+        enabled by default). To create the initial administrator user, run
+        {command}`pufferpanel --workDir /var/lib/pufferpanel user add --admin`.
+
+        Some options override corresponding settings set via web interface (e.g.
+        `PUFFER_PANEL_REGISTRATIONENABLED`). Those options can be temporarily
+        toggled or set in settings but do not persist between restarts.
+
+        [PufferPanel source code]: https://github.com/PufferPanel/PufferPanel/blob/master/config/entries.go
+      '';
+    };
+
+    environmentFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.path;
+      default = null;
+      description = lib.mdDoc ''
+        File to load environment variables from. Loaded variables override
+        values set in {option}`environment`.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.pufferpanel = {
+      description = "PufferPanel game management server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = cfg.extraPackages;
+      environment = cfg.environment;
+
+      # Note that we export environment variables for service directories if the
+      # value is not set. An empty environment variable is considered to be set.
+      # E.g.
+      #   export PUFFER_LOGS=${PUFFER_LOGS-$LOGS_DIRECTORY}
+      # would set PUFFER_LOGS to $LOGS_DIRECTORY if PUFFER_LOGS environment
+      # variable is not defined.
+      script = ''
+        ${lib.concatLines (lib.mapAttrsToList (name: value: ''
+          export ${name}="''${${name}-${value}}"
+        '') {
+          PUFFER_LOGS = "$LOGS_DIRECTORY";
+          PUFFER_DAEMON_DATA_CACHE = "$CACHE_DIRECTORY";
+          PUFFER_DAEMON_DATA_SERVERS = "$STATE_DIRECTORY/servers";
+          PUFFER_DAEMON_DATA_BINARIES = "$STATE_DIRECTORY/binaries";
+        })}
+        exec ${lib.getExe cfg.package} run --workDir "$STATE_DIRECTORY"
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+
+        UMask = "0077";
+
+        SupplementaryGroups = cfg.extraGroups;
+
+        StateDirectory = "pufferpanel";
+        StateDirectoryMode = "0700";
+        CacheDirectory = "pufferpanel";
+        CacheDirectoryMode = "0700";
+        LogsDirectory = "pufferpanel";
+        LogsDirectoryMode = "0700";
+
+        EnvironmentFile = cfg.environmentFile;
+
+        # Command "pufferpanel shutdown --pid $MAINPID" sends SIGTERM (code 15)
+        # to the main process and waits for termination. This is essentially
+        # KillMode=mixed we are using here. See
+        # https://freedesktop.org/software/systemd/man/systemd.kill.html#KillMode=
+        KillMode = "mixed";
+
+        DynamicUser = true;
+        ProtectHome = true;
+        ProtectProc = "invisible";
+        ProtectClock = true;
+        ProtectHostname = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        PrivateUsers = true;
+        PrivateDevices = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = [ "user" "mnt" ]; # allow buildFHSUserEnv
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+        LockPersonality = true;
+        DeviceAllow = [ "" ];
+        DevicePolicy = "closed";
+        CapabilityBoundingSet = [ "" ];
+      };
+    };
+  };
+
+  meta.maintainers = [ lib.maintainers.tie ];
+}
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 58a595b5c76f5..d881ea913695c 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -283,13 +283,13 @@ in
 
     services.redmine.settings = {
       production = {
-        scm_subversion_command = if cfg.components.subversion then "${pkgs.subversion}/bin/svn" else "";
-        scm_mercurial_command = if cfg.components.mercurial then "${pkgs.mercurial}/bin/hg" else "";
-        scm_git_command = if cfg.components.git then "${pkgs.git}/bin/git" else "";
-        scm_cvs_command = if cfg.components.cvs then "${pkgs.cvs}/bin/cvs" else "";
-        scm_bazaar_command = if cfg.components.breezy then "${pkgs.breezy}/bin/bzr" else "";
-        imagemagick_convert_command = if cfg.components.imagemagick then "${pkgs.imagemagick}/bin/convert" else "";
-        gs_command = if cfg.components.ghostscript then "${pkgs.ghostscript}/bin/gs" else "";
+        scm_subversion_command = optionalString cfg.components.subversion "${pkgs.subversion}/bin/svn";
+        scm_mercurial_command = optionalString cfg.components.mercurial "${pkgs.mercurial}/bin/hg";
+        scm_git_command = optionalString cfg.components.git "${pkgs.git}/bin/git";
+        scm_cvs_command = optionalString cfg.components.cvs "${pkgs.cvs}/bin/cvs";
+        scm_bazaar_command = optionalString cfg.components.breezy "${pkgs.breezy}/bin/bzr";
+        imagemagick_convert_command = optionalString cfg.components.imagemagick "${pkgs.imagemagick}/bin/convert";
+        gs_command = optionalString cfg.components.ghostscript "${pkgs.ghostscript}/bin/gs";
         minimagick_font_path = "${cfg.components.minimagick_font_path}";
       };
     };
diff --git a/nixos/modules/services/misc/rshim.nix b/nixos/modules/services/misc/rshim.nix
new file mode 100644
index 0000000000000..0fef2cc228c91
--- /dev/null
+++ b/nixos/modules/services/misc/rshim.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.rshim;
+
+  rshimCommand = [ "${cfg.package}/bin/rshim" ]
+    ++ lib.optionals (cfg.backend != null) [ "--backend ${cfg.backend}" ]
+    ++ lib.optionals (cfg.device != null) [ "--device ${cfg.device}" ]
+    ++ lib.optionals (cfg.index != null) [ "--index ${builtins.toString cfg.index}" ]
+    ++ [ "--log-level ${builtins.toString cfg.log-level}" ]
+  ;
+in
+{
+  options.services.rshim = {
+    enable = lib.mkEnableOption (lib.mdDoc "User-space rshim driver for the BlueField SoC");
+
+    package = lib.mkPackageOptionMD pkgs "rshim-user-space" { };
+
+    backend = lib.mkOption {
+      type = with lib.types; nullOr (enum [ "usb" "pcie" "pcie_lf" ]);
+      description = lib.mdDoc ''
+        Specify the backend to attach. If not specified, the driver will scan
+        all rshim backends unless the `device` option is given with a device
+        name specified.
+      '';
+      default = null;
+      example = "pcie";
+    };
+
+    device = lib.mkOption {
+      type = with lib.types; nullOr str;
+      description = lib.mdDoc ''
+        Specify the device name to attach. The backend driver can be deduced
+        from the device name, thus the `backend` option is not needed.
+      '';
+      default = null;
+      example = "pcie-04:00.2";
+    };
+
+    index = lib.mkOption {
+      type = with lib.types; nullOr int;
+      description = lib.mdDoc ''
+        Specify the index to create device path `/dev/rshim<index>`. It's also
+        used to create network interface name `tmfifo_net<index>`. This option
+        is needed when multiple rshim instances are running.
+      '';
+      default = null;
+      example = 1;
+    };
+
+    log-level = lib.mkOption {
+      type = lib.types.int;
+      description = lib.mdDoc ''
+        Specify the log level (0:none, 1:error, 2:warning, 3:notice, 4:debug).
+      '';
+      default = 2;
+      example = 4;
+    };
+
+    config = lib.mkOption {
+      type = with lib.types; attrsOf (oneOf [ int str ]);
+      description = lib.mdDoc ''
+        Structural setting for the rshim configuration file
+        (`/etc/rshim.conf`). It can be used to specify the static mapping
+        between rshim devices and rshim names. It can also be used to ignore
+        some rshim devices.
+      '';
+      default = { };
+      example = {
+        DISPLAY_LEVEL = 0;
+        rshim0 = "usb-2-1.7";
+        none = "usb-1-1.4";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.etc = lib.mkIf (cfg.config != { }) {
+      "rshim.conf".text = lib.generators.toKeyValue
+        { mkKeyValue = lib.generators.mkKeyValueDefault { } " "; }
+        cfg.config;
+    };
+
+    systemd.services.rshim = {
+      after = [ "network.target" ];
+      serviceConfig = {
+        Restart = "always";
+        Type = "forking";
+        ExecStart = [
+          (lib.concatStringsSep " \\\n" rshimCommand)
+        ];
+        KillMode = "control-group";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+}
diff --git a/nixos/modules/services/misc/siproxd.nix b/nixos/modules/services/misc/siproxd.nix
index f1a1ed4d29b38..99b25bdb8e9ed 100644
--- a/nixos/modules/services/misc/siproxd.nix
+++ b/nixos/modules/services/misc/siproxd.nix
@@ -20,7 +20,7 @@ let
     ${optionalString (cfg.hostsAllowReg != []) "hosts_allow_reg = ${concatStringsSep "," cfg.hostsAllowReg}"}
     ${optionalString (cfg.hostsAllowSip != []) "hosts_allow_sip = ${concatStringsSep "," cfg.hostsAllowSip}"}
     ${optionalString (cfg.hostsDenySip != []) "hosts_deny_sip  = ${concatStringsSep "," cfg.hostsDenySip}"}
-    ${if (cfg.passwordFile != "") then "proxy_auth_pwfile = ${cfg.passwordFile}" else ""}
+    ${optionalString (cfg.passwordFile != "") "proxy_auth_pwfile = ${cfg.passwordFile}"}
     ${cfg.extraConfig}
   '';
 
diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix
index 30ed30e7a8fa3..569433c3c71d1 100644
--- a/nixos/modules/services/misc/snapper.nix
+++ b/nixos/modules/services/misc/snapper.nix
@@ -4,6 +4,81 @@ with lib;
 
 let
   cfg = config.services.snapper;
+
+  mkValue = v:
+    if isList v then "\"${concatMapStringsSep " " (escape [ "\\" " " ]) v}\""
+    else if v == true then "yes"
+    else if v == false then "no"
+    else if isString v then "\"${v}\""
+    else builtins.toJSON v;
+
+  mkKeyValue = k: v: "${k}=${mkValue v}";
+
+  # "it's recommended to always specify the filesystem type"  -- man snapper-configs
+  defaultOf = k: if k == "FSTYPE" then null else configOptions.${k}.default or null;
+
+  safeStr = types.strMatching "[^\n\"]*" // {
+    description = "string without line breaks or quotes";
+    descriptionClass = "conjunction";
+  };
+
+  configOptions = {
+    SUBVOLUME = mkOption {
+      type = types.path;
+      description = lib.mdDoc ''
+        Path of the subvolume or mount point.
+        This path is a subvolume and has to contain a subvolume named
+        .snapshots.
+        See also man:snapper(8) section PERMISSIONS.
+      '';
+    };
+
+    FSTYPE = mkOption {
+      type = types.enum [ "btrfs" ];
+      default = "btrfs";
+      description = lib.mdDoc ''
+        Filesystem type. Only btrfs is stable and tested.
+      '';
+    };
+
+    ALLOW_GROUPS = mkOption {
+      type = types.listOf safeStr;
+      default = [];
+      description = lib.mdDoc ''
+        List of groups allowed to operate with the config.
+
+        Also see the PERMISSIONS section in man:snapper(8).
+      '';
+    };
+
+    ALLOW_USERS = mkOption {
+      type = types.listOf safeStr;
+      default = [];
+      example = [ "alice" ];
+      description = lib.mdDoc ''
+        List of users allowed to operate with the config. "root" is always
+        implicitly included.
+
+        Also see the PERMISSIONS section in man:snapper(8).
+      '';
+    };
+
+    TIMELINE_CLEANUP = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Defines whether the timeline cleanup algorithm should be run for the config.
+      '';
+    };
+
+    TIMELINE_CREATE = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Defines whether hourly snapshots should be created.
+      '';
+    };
+  };
 in
 
 {
@@ -52,49 +127,23 @@ in
       example = literalExpression ''
         {
           home = {
-            subvolume = "/home";
-            extraConfig = '''
-              ALLOW_USERS="alice"
-              TIMELINE_CREATE=yes
-              TIMELINE_CLEANUP=yes
-            ''';
+            SUBVOLUME = "/home";
+            ALLOW_USERS = [ "alice" ];
+            TIMELINE_CREATE = true;
+            TIMELINE_CLEANUP = true;
           };
         }
       '';
 
       description = lib.mdDoc ''
-        Subvolume configuration
+        Subvolume configuration. Any option mentioned in man:snapper-configs(5)
+        is valid here, even if NixOS doesn't document it.
       '';
 
       type = types.attrsOf (types.submodule {
-        options = {
-          subvolume = mkOption {
-            type = types.path;
-            description = lib.mdDoc ''
-              Path of the subvolume or mount point.
-              This path is a subvolume and has to contain a subvolume named
-              .snapshots.
-              See also man:snapper(8) section PERMISSIONS.
-            '';
-          };
-
-          fstype = mkOption {
-            type = types.enum [ "btrfs" ];
-            default = "btrfs";
-            description = lib.mdDoc ''
-              Filesystem type. Only btrfs is stable and tested.
-            '';
-          };
+        freeformType = types.attrsOf (types.oneOf [ (types.listOf safeStr) types.bool safeStr types.number ]);
 
-          extraConfig = mkOption {
-            type = types.lines;
-            default = "";
-            description = lib.mdDoc ''
-              Additional configuration next to SUBVOLUME and FSTYPE.
-              See man:snapper-configs(5).
-            '';
-          };
-        };
+        options = configOptions;
       });
     };
   };
@@ -117,11 +166,7 @@ in
 
       }
       // (mapAttrs' (name: subvolume: nameValuePair "snapper/configs/${name}" ({
-        text = ''
-          ${subvolume.extraConfig}
-          FSTYPE="${subvolume.fstype}"
-          SUBVOLUME="${subvolume.subvolume}"
-        '';
+        text = lib.generators.toKeyValue { inherit mkKeyValue; } (filterAttrs (k: v: v != defaultOf k) subvolume);
       })) cfg.configs)
       // (lib.optionalAttrs (cfg.filters != null) {
         "snapper/filters/default.txt".text = cfg.filters;
@@ -181,5 +226,28 @@ in
       unitConfig.ConditionPathExists = "/etc/snapper/configs/root";
     };
 
+    assertions =
+      concatMap
+        (name:
+          let
+            sub = cfg.configs.${name};
+          in
+          [ { assertion = !(sub ? extraConfig);
+              message = ''
+                The option definition `services.snapper.configs.${name}.extraConfig' no longer has any effect; please remove it.
+                The contents of this option should be migrated to attributes on `services.snapper.configs.${name}'.
+              '';
+            }
+          ] ++
+          map
+            (attr: {
+              assertion = !(hasAttr attr sub);
+              message = ''
+                The option definition `services.snapper.configs.${name}.${attr}' has been renamed to `services.snapper.configs.${name}.${toUpper attr}'.
+              '';
+            })
+            [ "fstype" "subvolume" ]
+        )
+        (attrNames cfg.configs);
   });
 }
diff --git a/nixos/modules/services/misc/tandoor-recipes.nix b/nixos/modules/services/misc/tandoor-recipes.nix
index a349bcac93214..63d3e3d2a8577 100644
--- a/nixos/modules/services/misc/tandoor-recipes.nix
+++ b/nixos/modules/services/misc/tandoor-recipes.nix
@@ -9,6 +9,7 @@ let
   env = {
     GUNICORN_CMD_ARGS = "--bind=${cfg.address}:${toString cfg.port}";
     DEBUG = "0";
+    DEBUG_TOOLBAR = "0";
     MEDIA_ROOT = "/var/lib/tandoor-recipes";
   } // optionalAttrs (config.time.timeZone != null) {
     TIMEZONE = config.time.timeZone;
diff --git a/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix
index 15deef18b60f3..58a0faed962cf 100644
--- a/nixos/modules/services/monitoring/datadog-agent.nix
+++ b/nixos/modules/services/monitoring/datadog-agent.nix
@@ -235,7 +235,7 @@ in {
 
     systemd.services = let
       makeService = attrs: recursiveUpdate {
-        path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.iproute2 ];
+        path = [ datadogPkg pkgs.sysstat pkgs.procps pkgs.iproute2 ];
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
           User = "datadog";
diff --git a/nixos/modules/services/monitoring/grafana-agent.nix b/nixos/modules/services/monitoring/grafana-agent.nix
index b7761c34fe51a..a462e760f748b 100644
--- a/nixos/modules/services/monitoring/grafana-agent.nix
+++ b/nixos/modules/services/monitoring/grafana-agent.nix
@@ -13,12 +13,7 @@ in
   options.services.grafana-agent = {
     enable = mkEnableOption (lib.mdDoc "grafana-agent");
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.grafana-agent;
-      defaultText = lib.literalExpression "pkgs.grafana-agent";
-      description = lib.mdDoc "The grafana-agent package to use.";
-    };
+    package = mkPackageOptionMD pkgs "grafana-agent" { };
 
     credentials = mkOption {
       description = lib.mdDoc ''
@@ -37,11 +32,22 @@ in
       };
     };
 
+    extraFlags = mkOption {
+      type = with types; listOf str;
+      default = [ ];
+      example = [ "-enable-features=integrations-next" "-disable-reporting" ];
+      description = lib.mdDoc ''
+        Extra command-line flags passed to {command}`grafana-agent`.
+
+        See <https://grafana.com/docs/agent/latest/static/configuration/flags/>
+      '';
+    };
+
     settings = mkOption {
       description = lib.mdDoc ''
-        Configuration for `grafana-agent`.
+        Configuration for {command}`grafana-agent`.
 
-        See https://grafana.com/docs/agent/latest/configuration/
+        See <https://grafana.com/docs/agent/latest/configuration/>
       '';
 
       type = types.submodule {
@@ -140,7 +146,7 @@ in
         # We can't use Environment=HOSTNAME=%H, as it doesn't include the domain part.
         export HOSTNAME=$(< /proc/sys/kernel/hostname)
 
-        exec ${lib.getExe cfg.package} -config.expand-env -config.file ${configFile}
+        exec ${lib.getExe cfg.package} -config.expand-env -config.file ${configFile} ${escapeShellArgs cfg.extraFlags}
       '';
       serviceConfig = {
         Restart = "always";
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 5a8c65b9dc3fb..e74ee641db386 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -92,17 +92,6 @@ let
   grafanaTypes.datasourceConfig = types.submodule {
     freeformType = provisioningSettingsFormat.type;
 
-    imports = [
-      (mkRemovedOptionModule [ "password" ] ''
-        `services.grafana.provision.datasources.settings.datasources.<name>.password` has been removed
-        in Grafana 9. Use `secureJsonData` instead.
-      '')
-      (mkRemovedOptionModule [ "basicAuthPassword" ] ''
-        `services.grafana.provision.datasources.settings.datasources.<name>.basicAuthPassword` has been removed
-        in Grafana 9. Use `secureJsonData` instead.
-      '')
-    ];
-
     options = {
       name = mkOption {
         type = types.str;
@@ -603,7 +592,6 @@ in {
                   description = lib.mdDoc "List of datasources to insert/update.";
                   default = [];
                   type = types.listOf grafanaTypes.datasourceConfig;
-                  apply = map (flip builtins.removeAttrs [ "password" "basicAuthPassword" ]);
                 };
 
                 deleteDatasources = mkOption {
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 92c870bb23f1d..bd0dea83e1a89 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -169,6 +169,20 @@ in {
           See: <https://learn.netdata.cloud/docs/agent/anonymous-statistics>
         '';
       };
+
+      deadlineBeforeStopSec = mkOption {
+        type = types.int;
+        default = 120;
+        description = lib.mdDoc ''
+          In order to detect when netdata is misbehaving, we run a concurrent task pinging netdata (wait-for-netdata-up)
+          in the systemd unit.
+
+          If after a while, this task does not succeed, we stop the unit and mark it as failed.
+
+          You can control this deadline in seconds with this option, it's useful to bump it
+          if you have (1) a lot of data (2) doing upgrades (3) have low IOPS/throughput.
+        '';
+      };
     };
   };
 
@@ -205,7 +219,7 @@ in {
           while [ "$(${pkgs.netdata}/bin/netdatacli ping)" != pong ]; do sleep 0.5; done
         '';
 
-        TimeoutStopSec = 60;
+        TimeoutStopSec = cfg.deadlineBeforeStopSec;
         Restart = "on-failure";
         # User and group
         User = cfg.user;
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 0c0931d3d295a..987f17c2c6e68 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -6,10 +6,12 @@ let
   cfg = config.services.prometheus.alertmanager;
   mkConfigFile = pkgs.writeText "alertmanager.yml" (builtins.toJSON cfg.configuration);
 
-  checkedConfig = file: pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
-    ln -s ${file} $out
-    amtool check-config $out
-  '';
+  checkedConfig = file:
+    if cfg.checkConfig then
+      pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
+        ln -s ${file} $out
+        amtool check-config $out
+      '' else file;
 
   alertmanagerYml = let
     yml = if cfg.configText != null then
@@ -70,6 +72,20 @@ in {
         '';
       };
 
+      checkConfig = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc ''
+          Check configuration with `amtool check-config`. The call to `amtool` is
+          subject to sandboxing by Nix.
+
+          If you use credentials stored in external files
+          (`environmentFile`, etc),
+          they will not be visible to `amtool`
+          and it will report errors, despite a correct configuration.
+        '';
+      };
+
       logFormat = mkOption {
         type = types.nullOr types.str;
         default = null;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.md b/nixos/modules/services/monitoring/prometheus/exporters.md
index c085e46d20d7d..34fadecadc749 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.md
+++ b/nixos/modules/services/monitoring/prometheus/exporters.md
@@ -76,7 +76,7 @@ example:
     directory, which will be called postfix.nix and contains all exporter
     specific options and configuration:
     ```
-    # nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
+    # nixpkgs/nixos/modules/services/prometheus/exporters/postfix.nix
     { config, lib, pkgs, options }:
 
     with lib;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
index 0c2de683ecf72..f67596f05a3a1 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
@@ -58,10 +58,10 @@ in
     };
   };
   serviceOpts = let
-    collectSettingsArgs = if (cfg.collectdBinary.enable) then ''
+    collectSettingsArgs = optionalString (cfg.collectdBinary.enable) ''
       --collectd.listen-address ${cfg.collectdBinary.listenAddress}:${toString cfg.collectdBinary.port} \
       --collectd.security-level ${cfg.collectdBinary.securityLevel} \
-    '' else "";
+    '';
   in {
     serviceConfig = {
       ExecStart = ''
diff --git a/nixos/modules/services/monitoring/uptime-kuma.nix b/nixos/modules/services/monitoring/uptime-kuma.nix
index 5f803d57b5e97..7027046b24253 100644
--- a/nixos/modules/services/monitoring/uptime-kuma.nix
+++ b/nixos/modules/services/monitoring/uptime-kuma.nix
@@ -43,6 +43,8 @@ in
     services.uptime-kuma.settings = {
       DATA_DIR = "/var/lib/uptime-kuma/";
       NODE_ENV = mkDefault "production";
+      HOST = mkDefault "127.0.0.1";
+      PORT = mkDefault "3001";
     };
 
     systemd.services.uptime-kuma = {
diff --git a/nixos/modules/services/network-filesystems/openafs/lib.nix b/nixos/modules/services/network-filesystems/openafs/lib.nix
index 80628f4dfaf29..e5e147a8dc338 100644
--- a/nixos/modules/services/network-filesystems/openafs/lib.nix
+++ b/nixos/modules/services/network-filesystems/openafs/lib.nix
@@ -1,13 +1,13 @@
 { config, lib, ...}:
 
 let
-  inherit (lib) concatStringsSep mkOption types;
+  inherit (lib) concatStringsSep mkOption types optionalString;
 
 in {
 
   mkCellServDB = cellName: db: ''
     >${cellName}
-  '' + (concatStringsSep "\n" (map (dbm: if (dbm.ip != "" && dbm.dnsname != "") then dbm.ip + " #" + dbm.dnsname else "")
+  '' + (concatStringsSep "\n" (map (dbm: optionalString (dbm.ip != "" && dbm.dnsname != "") "${dbm.ip} #${dbm.dnsname}")
                                    db))
      + "\n";
 
diff --git a/nixos/modules/services/network-filesystems/webdav-server-rs.nix b/nixos/modules/services/network-filesystems/webdav-server-rs.nix
index 9ea304111819b..34e717025e645 100644
--- a/nixos/modules/services/network-filesystems/webdav-server-rs.nix
+++ b/nixos/modules/services/network-filesystems/webdav-server-rs.nix
@@ -28,6 +28,12 @@ in
         description = lib.mdDoc "Group to run under when setuid is not enabled.";
       };
 
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = lib.mdDoc "Enable debug mode.";
+      };
+
       settings = mkOption {
         type = format.type;
         default = { };
@@ -111,7 +117,7 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        ExecStart = "${pkgs.webdav-server-rs}/bin/webdav-server -c ${cfg.configFile}";
+        ExecStart = "${pkgs.webdav-server-rs}/bin/webdav-server ${lib.optionalString cfg.debug "--debug"} -c ${cfg.configFile}";
 
         CapabilityBoundingSet = [
           "CAP_SETUID"
diff --git a/nixos/modules/services/networking/alice-lg.nix b/nixos/modules/services/networking/alice-lg.nix
new file mode 100644
index 0000000000000..06b9ac89f12fc
--- /dev/null
+++ b/nixos/modules/services/networking/alice-lg.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.alice-lg;
+  settingsFormat = pkgs.formats.ini { };
+in
+{
+  options = {
+    services.alice-lg = {
+      enable = mkEnableOption (lib.mdDoc "Alice Looking Glass");
+
+      package = mkPackageOptionMD pkgs "alice-lg" { };
+
+      settings = mkOption {
+        type = settingsFormat.type;
+        default = { };
+        description = lib.mdDoc ''
+          alice-lg configuration, for configuration options see the example on [github](https://github.com/alice-lg/alice-lg/blob/main/etc/alice-lg/alice.example.conf)
+        '';
+        example = literalExpression ''
+          {
+            server = {
+              # configures the built-in webserver and provides global application settings
+              listen_http = "127.0.0.1:7340";
+              enable_prefix_lookup = true;
+              asn = 9033;
+              store_backend = postgres;
+              routes_store_refresh_parallelism = 5;
+              neighbors_store_refresh_parallelism = 10000;
+              routes_store_refresh_interval = 5;
+              neighbors_store_refresh_interval = 5;
+            };
+            postgres = {
+              url = "postgres://postgres:postgres@localhost:5432/alice";
+              min_connections = 2;
+              max_connections = 128;
+            };
+            pagination = {
+              routes_filtered_page_size = 250;
+              routes_accepted_page_size = 250;
+              routes_not_exported_page_size = 250;
+            };
+          }
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment = {
+      etc."alice-lg/alice.conf".source = settingsFormat.generate "alice-lg.conf" cfg.settings;
+    };
+    systemd.services = {
+      alice-lg = {
+        wants = [ "network.target" ];
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        description = "Alice Looking Glass";
+        serviceConfig = {
+          DynamicUser = true;
+          Type = "simple";
+          Restart = "on-failure";
+          RestartSec = 15;
+          ExecStart = "${cfg.package}/bin/alice-lg";
+          StateDirectoryMode = "0700";
+          UMask = "0007";
+          CapabilityBoundingSet = "";
+          NoNewPrivileges = true;
+          ProtectSystem = "strict";
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateUsers = true;
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_INET AF_INET6" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          PrivateMounts = true;
+          SystemCallArchitectures = "native";
+          SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+          BindReadOnlyPaths = [
+            "-/etc/resolv.conf"
+            "-/etc/nsswitch.conf"
+            "-/etc/ssl/certs"
+            "-/etc/static/ssl/certs"
+            "-/etc/hosts"
+            "-/etc/localtime"
+          ];
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/bird-lg.nix b/nixos/modules/services/networking/bird-lg.nix
index 11cfe3e7ec015..dc861dbfd11bd 100644
--- a/nixos/modules/services/networking/bird-lg.nix
+++ b/nixos/modules/services/networking/bird-lg.nix
@@ -4,6 +4,49 @@ with lib;
 
 let
   cfg = config.services.bird-lg;
+
+  stringOrConcat = sep: v: if builtins.isString v then v else concatStringsSep sep v;
+
+  frontend_args = let
+    fe = cfg.frontend;
+  in {
+    "--servers" = concatStringsSep "," fe.servers;
+    "--domain" = fe.domain;
+    "--listen" = fe.listenAddress;
+    "--proxy-port" = fe.proxyPort;
+    "--whois" = fe.whois;
+    "--dns-interface" = fe.dnsInterface;
+    "--bgpmap-info" = concatStringsSep "," cfg.frontend.bgpMapInfo;
+    "--title-brand" = fe.titleBrand;
+    "--navbar-brand" = fe.navbar.brand;
+    "--navbar-brand-url" = fe.navbar.brandURL;
+    "--navbar-all-servers" = fe.navbar.allServers;
+    "--navbar-all-url" = fe.navbar.allServersURL;
+    "--net-specific-mode" = fe.netSpecificMode;
+    "--protocol-filter" = concatStringsSep "," cfg.frontend.protocolFilter;
+  };
+
+  proxy_args = let
+    px = cfg.proxy;
+  in {
+    "--allowed" = concatStringsSep "," px.allowedIPs;
+    "--bird" = px.birdSocket;
+    "--listen" = px.listenAddress;
+    "--traceroute_bin" = px.traceroute.binary;
+    "--traceroute_flags" = concatStringsSep " " px.traceroute.flags;
+    "--traceroute_raw" = px.traceroute.rawOutput;
+  };
+
+  mkArgValue = value:
+    if isString value
+      then escapeShellArg value
+      else if isBool value
+        then boolToString value
+        else toString value;
+
+  filterNull = filterAttrs (_: v: v != "" && v != null && v != []);
+
+  argsAttrToList = args: mapAttrsToList (name: value: "${name} " + mkArgValue value ) (filterNull args);
 in
 {
   options = {
@@ -44,14 +87,12 @@ in
 
         domain = mkOption {
           type = types.str;
-          default = "";
           example = "dn42.lantian.pub";
           description = lib.mdDoc "Server name domain suffixes.";
         };
 
         servers = mkOption {
           type = types.listOf types.str;
-          default = [ ];
           example = [ "gigsgigscloud" "hostdare" ];
           description = lib.mdDoc "Server name prefixes.";
         };
@@ -134,10 +175,14 @@ in
         };
 
         extraArgs = mkOption {
-          type = types.lines;
-          default = "";
+          type = with types; either lines (listOf str);
+          default = [ ];
           description = lib.mdDoc ''
             Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#frontend).
+
+            :::{.note}
+            Passing lines (plain strings) is deprecated in favour of passing lists of strings.
+            :::
           '';
         };
       };
@@ -160,8 +205,7 @@ in
 
         birdSocket = mkOption {
           type = types.str;
-          default = "/run/bird.ctl";
-          example = "/var/run/bird/bird.ctl";
+          default = "/var/run/bird/bird.ctl";
           description = lib.mdDoc "Bird control socket path.";
         };
 
@@ -173,6 +217,12 @@ in
             description = lib.mdDoc "Traceroute's binary path.";
           };
 
+          flags = mkOption {
+            type = with types; listOf str;
+            default = [ ];
+            description = lib.mdDoc "Flags for traceroute process";
+          };
+
           rawOutput = mkOption {
             type = types.bool;
             default = false;
@@ -181,10 +231,14 @@ in
         };
 
         extraArgs = mkOption {
-          type = types.lines;
-          default = "";
+          type = with types; either lines (listOf str);
+          default = [ ];
           description = lib.mdDoc ''
             Extra parameters documented [here](https://github.com/xddxdd/bird-lg-go#proxy).
+
+            :::{.note}
+            Passing lines (plain strings) is deprecated in favour of passing lists of strings.
+            :::
           '';
         };
       };
@@ -194,6 +248,16 @@ in
   ###### implementation
 
   config = {
+
+    warnings =
+      lib.optional (cfg.frontend.enable  && builtins.isString cfg.frontend.extraArgs) ''
+        Passing strings to `services.bird-lg.frontend.extraOptions' is deprecated. Please pass a list of strings instead.
+      ''
+      ++ lib.optional (cfg.proxy.enable  && builtins.isString cfg.proxy.extraArgs) ''
+        Passing strings to `services.bird-lg.proxy.extraOptions' is deprecated. Please pass a list of strings instead.
+      ''
+    ;
+
     systemd.services = {
       bird-lg-frontend = mkIf cfg.frontend.enable {
         enable = true;
@@ -211,23 +275,8 @@ in
         };
         script = ''
           ${cfg.package}/bin/frontend \
-            --servers ${concatStringsSep "," cfg.frontend.servers } \
-            --domain ${cfg.frontend.domain} \
-            --listen ${cfg.frontend.listenAddress} \
-            --proxy-port ${toString cfg.frontend.proxyPort} \
-            --whois ${cfg.frontend.whois} \
-            --dns-interface ${cfg.frontend.dnsInterface} \
-            --bgpmap-info ${concatStringsSep "," cfg.frontend.bgpMapInfo } \
-            --title-brand ${cfg.frontend.titleBrand} \
-            --navbar-brand ${cfg.frontend.navbar.brand} \
-            --navbar-brand-url ${cfg.frontend.navbar.brandURL} \
-            --navbar-all-servers ${cfg.frontend.navbar.allServers} \
-            --navbar-all-url ${cfg.frontend.navbar.allServersURL} \
-            --net-specific-mode ${cfg.frontend.netSpecificMode} \
-            --protocol-filter ${concatStringsSep "," cfg.frontend.protocolFilter } \
-            --name-filter ${cfg.frontend.nameFilter} \
-            --time-out ${toString cfg.frontend.timeout} \
-            ${cfg.frontend.extraArgs}
+            ${concatStringsSep " \\\n  " (argsAttrToList frontend_args)} \
+            ${stringOrConcat " " cfg.frontend.extraArgs}
         '';
       };
 
@@ -247,12 +296,8 @@ in
         };
         script = ''
           ${cfg.package}/bin/proxy \
-          --allowed ${concatStringsSep "," cfg.proxy.allowedIPs } \
-          --bird ${cfg.proxy.birdSocket} \
-          --listen ${cfg.proxy.listenAddress} \
-          --traceroute_bin ${cfg.proxy.traceroute.binary}
-          --traceroute_raw ${boolToString cfg.proxy.traceroute.rawOutput}
-          ${cfg.proxy.extraArgs}
+            ${concatStringsSep " \\\n  " (argsAttrToList proxy_args)} \
+            ${stringOrConcat " " cfg.proxy.extraArgs}
         '';
       };
     };
@@ -266,4 +311,9 @@ in
       };
     };
   };
+
+  meta.maintainers = with lib.maintainers; [
+    e1mo
+    tchekda
+  ];
 }
diff --git a/nixos/modules/services/networking/birdwatcher.nix b/nixos/modules/services/networking/birdwatcher.nix
new file mode 100644
index 0000000000000..a129b7a2b4cf5
--- /dev/null
+++ b/nixos/modules/services/networking/birdwatcher.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.birdwatcher;
+in
+{
+  options = {
+    services.birdwatcher = {
+      package = mkOption {
+        type = types.package;
+        default = pkgs.birdwatcher;
+        defaultText = literalExpression "pkgs.birdwatcher";
+        description = lib.mdDoc "The Birdwatcher package to use.";
+      };
+      enable = mkEnableOption (lib.mdDoc "Birdwatcher");
+      flags = mkOption {
+        default = [ ];
+        type = types.listOf types.str;
+        example = [ "-worker-pool-size 16" "-6" ];
+        description = lib.mdDoc ''
+          Flags to append to the program call
+        '';
+      };
+
+      settings = mkOption {
+        type = types.lines;
+        default = { };
+        description = lib.mdDoc ''
+          birdwatcher configuration, for configuration options see the example on [github](https://github.com/alice-lg/birdwatcher/blob/master/etc/birdwatcher/birdwatcher.conf)
+        '';
+        example = literalExpression ''
+          [server]
+          allow_from = []
+          allow_uncached = false
+          modules_enabled = ["status",
+                             "protocols",
+                             "protocols_bgp",
+                             "protocols_short",
+                             "routes_protocol",
+                             "routes_peer",
+                             "routes_table",
+                             "routes_table_filtered",
+                             "routes_table_peer",
+                             "routes_filtered",
+                             "routes_prefixed",
+                             "routes_noexport",
+                             "routes_pipe_filtered_count",
+                             "routes_pipe_filtered"
+                            ]
+
+          [status]
+          reconfig_timestamp_source = "bird"
+          reconfig_timestamp_match = "# created: (.*)"
+
+          filter_fields = []
+
+          [bird]
+          listen = "0.0.0.0:29184"
+          config = "/etc/bird/bird2.conf"
+          birdc  = "''${pkgs.bird}/bin/birdc"
+          ttl = 5 # time to live (in minutes) for caching of cli output
+
+          [parser]
+          filter_fields = []
+
+          [cache]
+          use_redis = false # if not using redis cache, activate housekeeping to save memory!
+
+          [housekeeping]
+          interval = 5
+          force_release_memory = true
+        '';
+      };
+    };
+  };
+
+  config =
+    let flagsStr = escapeShellArgs cfg.flags;
+    in lib.mkIf cfg.enable {
+      environment.etc."birdwatcher/birdwatcher.conf".source = pkgs.writeTextFile {
+        name = "birdwatcher.conf";
+        text = cfg.settings;
+      };
+      systemd.services = {
+        birdwatcher = {
+          wants = [ "network.target" ];
+          after = [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+          description = "Birdwatcher";
+          serviceConfig = {
+            Type = "simple";
+            Restart = "on-failure";
+            RestartSec = 15;
+            ExecStart = "${cfg.package}/bin/birdwatcher";
+            StateDirectoryMode = "0700";
+            UMask = "0117";
+            NoNewPrivileges = true;
+            ProtectSystem = "strict";
+            PrivateTmp = true;
+            PrivateDevices = true;
+            ProtectHostname = true;
+            ProtectClock = true;
+            ProtectKernelTunables = true;
+            ProtectKernelModules = true;
+            ProtectKernelLogs = true;
+            ProtectControlGroups = true;
+            RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6" ];
+            LockPersonality = true;
+            MemoryDenyWriteExecute = true;
+            RestrictRealtime = true;
+            RestrictSUIDSGID = true;
+            PrivateMounts = true;
+            SystemCallArchitectures = "native";
+            SystemCallFilter = "~@clock @privileged @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @reboot @setuid @swap";
+            BindReadOnlyPaths = [
+              "-/etc/resolv.conf"
+              "-/etc/nsswitch.conf"
+              "-/etc/ssl/certs"
+              "-/etc/static/ssl/certs"
+              "-/etc/hosts"
+              "-/etc/localtime"
+            ];
+          };
+        };
+      };
+    };
+}
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index f1c36138be3e4..955463b9031eb 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -199,7 +199,7 @@ in
             (filterAttrs (n: _: hasPrefix "consul.d/" n) config.environment.etc);
 
         serviceConfig = {
-          ExecStart = "@${cfg.package}/bin/consul consul agent -config-dir /etc/consul.d"
+          ExecStart = "@${lib.getExe cfg.package} consul agent -config-dir /etc/consul.d"
             + concatMapStrings (n: " -config-file ${n}") configFiles;
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           PermissionsStartOnly = true;
@@ -207,10 +207,10 @@ in
           Restart = "on-failure";
           TimeoutStartSec = "infinity";
         } // (optionalAttrs (cfg.leaveOnStop) {
-          ExecStop = "${cfg.package}/bin/consul leave";
+          ExecStop = "${lib.getExe cfg.package} leave";
         });
 
-        path = with pkgs; [ iproute2 gnugrep gawk consul ];
+        path = with pkgs; [ iproute2 gawk cfg.package ];
         preStart = let
           family = if cfg.forceAddrFamily == "ipv6" then
             "-6"
@@ -269,7 +269,7 @@ in
 
         serviceConfig = {
           ExecStart = ''
-            ${cfg.alerts.package}/bin/consul-alerts start \
+            ${lib.getExe cfg.alerts.package} start \
               --alert-addr=${cfg.alerts.listenAddr} \
               --consul-addr=${cfg.alerts.consulAddr} \
               ${optionalString cfg.alerts.watchChecks "--watch-checks"} \
diff --git a/nixos/modules/services/networking/harmonia.nix b/nixos/modules/services/networking/harmonia.nix
new file mode 100644
index 0000000000000..144fa6c708e28
--- /dev/null
+++ b/nixos/modules/services/networking/harmonia.nix
@@ -0,0 +1,88 @@
+{ config, pkgs, lib, ... }:
+let
+  cfg = config.services.harmonia;
+  format = pkgs.formats.toml { };
+in
+{
+  options = {
+    services.harmonia = {
+      enable = lib.mkEnableOption (lib.mdDoc "Harmonia: Nix binary cache written in Rust");
+
+      signKeyPath = lib.mkOption {
+        type = lib.types.nullOr lib.types.path;
+        default = null;
+        description = lib.mdDoc "Path to the signing key that will be used for signing the cache";
+      };
+
+      package = lib.mkPackageOptionMD pkgs "harmonia" { };
+
+      settings = lib.mkOption {
+        inherit (format) type;
+        default = { };
+        description = lib.mdDoc ''
+          Settings to merge with the default configuration.
+          For the list of the default configuration, see <https://github.com/nix-community/harmonia/tree/master#configuration>.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.harmonia = {
+      description = "harmonia binary cache service";
+
+      requires = [ "nix-daemon.socket" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        CONFIG_FILE = format.generate "harmonia.toml" cfg.settings;
+        SIGN_KEY_PATH = lib.mkIf (cfg.signKeyPath != null) "%d/sign-key";
+        # Note: it's important to set this for nix-store, because it wants to use
+        # $HOME in order to use a temporary cache dir. bizarre failures will occur
+        # otherwise
+        HOME = "/run/harmonia";
+      };
+
+      serviceConfig = {
+        ExecStart = lib.getExe cfg.package;
+        User = "harmonia";
+        Group = "harmonia";
+        DynamicUser = true;
+        PrivateUsers = true;
+        DeviceAllow = [ "" ];
+        UMask = "0066";
+        RuntimeDirectory = "harmonia";
+        LoadCredential = lib.mkIf (cfg.signKeyPath != null) [ "sign-key:${cfg.signKeyPath}" ];
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+          "~@resources"
+        ];
+        CapabilityBoundingSet = "";
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectKernelLogs = true;
+        ProtectHostname = true;
+        ProtectClock = true;
+        RestrictRealtime = true;
+        MemoryDenyWriteExecute = true;
+        ProcSubset = "pid";
+        ProtectProc = "invisible";
+        RestrictNamespaces = true;
+        SystemCallArchitectures = "native";
+        PrivateNetwork = false;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        LockPersonality = true;
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+        LimitNOFILE = 65536;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/iscsi/root-initiator.nix b/nixos/modules/services/networking/iscsi/root-initiator.nix
index 4434fedce1eb8..895467cc674ab 100644
--- a/nixos/modules/services/networking/iscsi/root-initiator.nix
+++ b/nixos/modules/services/networking/iscsi/root-initiator.nix
@@ -185,6 +185,10 @@ in
         assertion = cfg.loginAll -> cfg.target == null;
         message = "iSCSI target name is set while login on all portals is enabled.";
       }
+      {
+        assertion = !config.boot.initrd.systemd.enable;
+        message = "systemd stage 1 does not support iscsi yet.";
+      }
     ];
   };
 }
diff --git a/nixos/modules/services/networking/ivpn.nix b/nixos/modules/services/networking/ivpn.nix
new file mode 100644
index 0000000000000..6df630c1f1947
--- /dev/null
+++ b/nixos/modules/services/networking/ivpn.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.ivpn;
+in
+with lib;
+{
+  options.services.ivpn = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        This option enables iVPN daemon.
+        This sets {option}`networking.firewall.checkReversePath` to "loose", which might be undesirable for security.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "tun" ];
+
+    environment.systemPackages = with pkgs; [ ivpn ivpn-service ];
+
+    # iVPN writes to /etc/iproute2/rt_tables
+    networking.iproute2.enable = true;
+    networking.firewall.checkReversePath = "loose";
+
+    systemd.services.ivpn-service = {
+      description = "iVPN daemon";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+      after = [
+        "network-online.target"
+        "NetworkManager.service"
+        "systemd-resolved.service"
+      ];
+      path = [
+        # Needed for mount
+        "/run/wrappers"
+      ];
+      startLimitBurst = 5;
+      startLimitIntervalSec = 20;
+      serviceConfig = {
+        ExecStart = "${pkgs.ivpn-service}/bin/ivpn-service --logging";
+        Restart = "always";
+        RestartSec = 1;
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ ataraxiasjel ];
+}
diff --git a/nixos/modules/services/networking/ndppd.nix b/nixos/modules/services/networking/ndppd.nix
index 98c58d2d5db1b..d221c95ae6200 100644
--- a/nixos/modules/services/networking/ndppd.nix
+++ b/nixos/modules/services/networking/ndppd.nix
@@ -17,7 +17,7 @@ let
       ttl ${toString proxy.ttl}
       ${render proxy.rules (ruleNetworkName: rule: ''
       rule ${prefer rule.network ruleNetworkName} {
-        ${rule.method}${if rule.method == "iface" then " ${rule.interface}" else ""}
+        ${rule.method}${optionalString (rule.method == "iface") " ${rule.interface}"}
       }'')}
     }'')}
   '');
diff --git a/nixos/modules/services/networking/netbird.nix b/nixos/modules/services/networking/netbird.nix
index 5bd9e9ca61696..647c0ce3e6d1f 100644
--- a/nixos/modules/services/networking/netbird.nix
+++ b/nixos/modules/services/networking/netbird.nix
@@ -41,9 +41,10 @@ in {
       documentation = [ "https://netbird.io/docs/" ];
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [
+        openresolv
+      ];
       serviceConfig = {
-        AmbientCapabilities = [ "CAP_NET_ADMIN" ];
-        DynamicUser = true;
         Environment = [
           "NB_CONFIG=/var/lib/netbird/config.json"
           "NB_LOG_FILE=console"
diff --git a/nixos/modules/services/networking/ntopng.nix b/nixos/modules/services/networking/ntopng.nix
index e6344d7ff3b34..bf7ec19f02a68 100644
--- a/nixos/modules/services/networking/ntopng.nix
+++ b/nixos/modules/services/networking/ntopng.nix
@@ -86,7 +86,7 @@ in
 
       redis.createInstance = mkOption {
         type = types.nullOr types.str;
-        default = if versionAtLeast config.system.stateVersion "22.05" then "ntopng" else "";
+        default = optionalString (versionAtLeast config.system.stateVersion "22.05") "ntopng";
         description = lib.mdDoc ''
           Local Redis instance name. Set to `null` to disable
           local Redis instance. Defaults to `""` for
diff --git a/nixos/modules/services/networking/picosnitch.nix b/nixos/modules/services/networking/picosnitch.nix
new file mode 100644
index 0000000000000..c9b38c1929ca1
--- /dev/null
+++ b/nixos/modules/services/networking/picosnitch.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.picosnitch;
+in
+{
+  options.services.picosnitch = {
+    enable = mkEnableOption (lib.mdDoc "picosnitch daemon");
+  };
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.picosnitch ];
+    systemd.services.picosnitch = {
+      description = "picosnitch";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+        RestartSec = 5;
+        ExecStart = "${pkgs.picosnitch}/bin/picosnitch start-no-daemon";
+        PIDFile = "/run/picosnitch/picosnitch.pid";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/ssh/lshd.nix b/nixos/modules/services/networking/ssh/lshd.nix
index 7932bac9ca3a1..af64969c2fcd4 100644
--- a/nixos/modules/services/networking/ssh/lshd.nix
+++ b/nixos/modules/services/networking/ssh/lshd.nix
@@ -169,11 +169,11 @@ in
             else (concatStrings (map (i: "--interface=\"${i}\"")
                                      interfaces))} \
           -h "${hostKey}" \
-          ${if !syslog then "--no-syslog" else ""} \
+          ${optionalString (!syslog) "--no-syslog" } \
           ${if passwordAuthentication then "--password" else "--no-password" } \
           ${if publicKeyAuthentication then "--publickey" else "--no-publickey" } \
           ${if rootLogin then "--root-login" else "--no-root-login" } \
-          ${if loginShell != null then "--login-shell=\"${loginShell}\"" else "" } \
+          ${optionalString (loginShell != null) "--login-shell=\"${loginShell}\"" } \
           ${if srpKeyExchange then "--srp-keyexchange" else "--no-srp-keyexchange" } \
           ${if !tcpForwarding then "--no-tcpip-forward" else "--tcpip-forward"} \
           ${if x11Forwarding then "--x11-forward" else "--no-x11-forward" } \
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index 4ec89be82b701..fd9650a058c2f 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -365,9 +365,6 @@ in
                 "hmac-sha2-512-etm@openssh.com"
                 "hmac-sha2-256-etm@openssh.com"
                 "umac-128-etm@openssh.com"
-                "hmac-sha2-512"
-                "hmac-sha2-256"
-                "umac-128@openssh.com"
               ];
               description = lib.mdDoc ''
                 Allowed MACs
@@ -474,10 +471,10 @@ in
                       mkdir -m 0755 -p "$(dirname '${k.path}')"
                       ssh-keygen \
                         -t "${k.type}" \
-                        ${if k ? bits then "-b ${toString k.bits}" else ""} \
-                        ${if k ? rounds then "-a ${toString k.rounds}" else ""} \
-                        ${if k ? comment then "-C '${k.comment}'" else ""} \
-                        ${if k ? openSSHFormat && k.openSSHFormat then "-o" else ""} \
+                        ${optionalString (k ? bits) "-b ${toString k.bits}"} \
+                        ${optionalString (k ? rounds) "-a ${toString k.rounds}"} \
+                        ${optionalString (k ? comment) "-C '${k.comment}'"} \
+                        ${optionalString (k ? openSSHFormat && k.openSSHFormat) "-o"} \
                         -f "${k.path}" \
                         -N ""
                   fi
@@ -550,7 +547,7 @@ in
         '') cfg.ports}
 
         ${concatMapStrings ({ port, addr, ... }: ''
-          ListenAddress ${addr}${if port != null then ":" + toString port else ""}
+          ListenAddress ${addr}${optionalString (port != null) (":" + toString port)}
         '') cfg.listenAddresses}
 
         ${optionalString cfgc.setXAuthLocation ''
diff --git a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
index 84ac4fef26efd..1ad5fdbcef026 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -225,20 +225,22 @@ in {
       irrespective of the value of this option (even when set to no).
     '';
 
-    childless = mkEnumParam [ "allow" "force" "never" ] "allow" ''
-      Use childless IKE_SA initiation (RFC 6023) for IKEv2.  Acceptable values
-      are `allow` (the default), `force` and
-      `never`. If set to `allow`, responders
+    childless = mkEnumParam [ "allow" "prefer" "force" "never" ] "allow" ''
+      Use childless IKE_SA initiation (_allow_, _prefer_, _force_ or _never_).
+
+      Use childless IKE_SA initiation (RFC 6023) for IKEv2, with the first
+      CHILD_SA created with a separate CREATE_CHILD_SA exchange (e.g. to use an
+      independent DH exchange for all CHILD_SAs).  Acceptable values are `allow`
+      (the default), `prefer`, `force` and `never`. If set to `allow`, responders
       will accept childless IKE_SAs (as indicated via notify in the IKE_SA_INIT
-      response) while initiators continue to create regular IKE_SAs with the
-      first CHILD_SA created during IKE_AUTH, unless the IKE_SA is initiated
-      explicitly without any children (which will fail if the responder does not
-      support or has disabled this extension).  If set to
-      `force`, only childless initiation is accepted and the
-      first CHILD_SA is created with a separate CREATE_CHILD_SA exchange
-      (e.g. to use an independent DH exchange for all CHILD_SAs). Finally,
-      setting the option to `never` disables support for
-      childless IKE_SAs as responder.
+      response) while initiators continue to create regular IKE_SAs with the first
+      CHILD_SA created during IKE_AUTH, unless the IKE_SA is initiated explicitly
+      without any children (which will fail if the responder does not support or
+      has disabled this extension). The effect of `prefer` is the same as `allow`
+      on responders, but as initiator a childless IKE_SA is initiated if the
+      responder supports it. If set to `force`, only childless initiation is
+      accepted in either role.  Finally, setting the option to `never` disables
+      support for childless IKE_SAs as responder.
     '';
 
     send_certreq = mkYesNoParam yes ''
@@ -357,11 +359,22 @@ in {
     if_id_in = mkStrParam "0" ''
       XFRM interface ID set on inbound policies/SA, can be overridden by child
       config, see there for details.
+
+      The special value `%unique` allocates a unique interface ID per IKE_SA,
+      which is inherited by all its CHILD_SAs (unless overridden there), beyond
+      that the value `%unique-dir` assigns a different unique interface ID for
+      each direction (in/out).
+
     '';
 
     if_id_out = mkStrParam "0" ''
       XFRM interface ID set on outbound policies/SA, can be overridden by child
       config, see there for details.
+
+      The special value `%unique` allocates a unique interface ID per IKE_SA,
+      which is inherited by all its CHILD_SAs (unless overridden there), beyond
+      that the value `%unique-dir` assigns a different unique interface ID for
+      each direction (in/out).
     '';
 
     mediation = mkYesNoParam no ''
@@ -985,12 +998,14 @@ in {
         protection.
       '';
 
-      hw_offload = mkEnumParam ["yes" "no" "auto"] "no" ''
+      hw_offload = mkEnumParam ["yes" "no" "auto" "crypto" "packet"] "no" ''
         Enable hardware offload for this CHILD_SA, if supported by the IPsec
-        implementation. The value `yes` enforces offloading
-        and the installation will fail if it's not supported by either kernel or
-        device. The value `auto` enables offloading, if it's
-        supported, but the installation does not fail otherwise.
+        implementation. The values `crypto` or `packet` enforce crypto or full
+        packet offloading and the installation will fail if the selected mode is not
+        supported by either kernel or device. On Linux, `packet` also offloads
+        policies, including trap policies. The value `auto` enables full packet
+        or crypto offloading, if either is supported, but the installation does not
+        fail otherwise.
       '';
 
       copy_df = mkYesNoParam yes ''
diff --git a/nixos/modules/services/networking/strongswan.nix b/nixos/modules/services/networking/strongswan.nix
index 8b1398bfd47d4..e58526814d1ad 100644
--- a/nixos/modules/services/networking/strongswan.nix
+++ b/nixos/modules/services/networking/strongswan.nix
@@ -4,7 +4,7 @@ let
 
   inherit (builtins) toFile;
   inherit (lib) concatMapStringsSep concatStringsSep mapAttrsToList
-                mkIf mkEnableOption mkOption types literalExpression;
+                mkIf mkEnableOption mkOption types literalExpression optionalString;
 
   cfg = config.services.strongswan;
 
@@ -34,8 +34,8 @@ let
 
   strongswanConf = {setup, connections, ca, secretsFile, managePlugins, enabledPlugins}: toFile "strongswan.conf" ''
     charon {
-      ${if managePlugins then "load_modular = no" else ""}
-      ${if managePlugins then ("load = " + (concatStringsSep " " enabledPlugins)) else ""}
+      ${optionalString managePlugins "load_modular = no"}
+      ${optionalString managePlugins ("load = " + (concatStringsSep " " enabledPlugins))}
       plugins {
         stroke {
           secrets_file = ${secretsFile}
diff --git a/nixos/modules/services/networking/stunnel.nix b/nixos/modules/services/networking/stunnel.nix
index 4f592fb312d33..996e9b2253921 100644
--- a/nixos/modules/services/networking/stunnel.nix
+++ b/nixos/modules/services/networking/stunnel.nix
@@ -154,8 +154,8 @@ in
     environment.systemPackages = [ pkgs.stunnel ];
 
     environment.etc."stunnel.cfg".text = ''
-      ${ if cfg.user != null then "setuid = ${cfg.user}" else "" }
-      ${ if cfg.group != null then "setgid = ${cfg.group}" else "" }
+      ${ optionalString (cfg.user != null) "setuid = ${cfg.user}" }
+      ${ optionalString (cfg.group != null) "setgid = ${cfg.group}" }
 
       debug = ${cfg.logLevel}
 
diff --git a/nixos/modules/services/networking/syncplay.nix b/nixos/modules/services/networking/syncplay.nix
index 726f65671072d..0a66d93bf153a 100644
--- a/nixos/modules/services/networking/syncplay.nix
+++ b/nixos/modules/services/networking/syncplay.nix
@@ -8,7 +8,8 @@ let
   cmdArgs =
     [ "--port" cfg.port ]
     ++ optionals (cfg.salt != null) [ "--salt" cfg.salt ]
-    ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ];
+    ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ]
+    ++ cfg.extraArgs;
 
 in
 {
@@ -33,7 +34,22 @@ in
         default = null;
         description = lib.mdDoc ''
           Salt to allow room operator passwords generated by this server
-          instance to still work when the server is restarted.
+          instance to still work when the server is restarted.  The salt will be
+          readable in the nix store and the processlist.  If this is not
+          intended use `saltFile` instead.  Mutually exclusive with
+          <option>services.syncplay.saltFile</option>.
+        '';
+      };
+
+      saltFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = lib.mdDoc ''
+          Path to the file that contains the server salt.  This allows room
+          operator passwords generated by this server instance to still work
+          when the server is restarted.  `null`, the server doesn't load the
+          salt from a file.  Mutually exclusive with
+          <option>services.syncplay.salt</option>.
         '';
       };
 
@@ -46,6 +62,14 @@ in
         '';
       };
 
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = lib.mdDoc ''
+          Additional arguments to be passed to the service.
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "nobody";
@@ -74,21 +98,31 @@ in
   };
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.salt == null || cfg.saltFile == null;
+        message = "services.syncplay.salt and services.syncplay.saltFile are mutually exclusive.";
+      }
+    ];
     systemd.services.syncplay = {
       description = "Syncplay Service";
-      wantedBy    = [ "multi-user.target" ];
-      after       = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
 
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
-        LoadCredential = lib.mkIf (cfg.passwordFile != null) "password:${cfg.passwordFile}";
+        LoadCredential = lib.optional (cfg.passwordFile != null) "password:${cfg.passwordFile}"
+          ++ lib.optional (cfg.saltFile != null) "salt:${cfg.saltFile}";
       };
 
       script = ''
         ${lib.optionalString (cfg.passwordFile != null) ''
           export SYNCPLAY_PASSWORD=$(cat "''${CREDENTIALS_DIRECTORY}/password")
         ''}
+        ${lib.optionalString (cfg.saltFile != null) ''
+          export SYNCPLAY_SALT=$(cat "''${CREDENTIALS_DIRECTORY}/salt")
+        ''}
         exec ${pkgs.syncplay-nogui}/bin/syncplay-server ${escapeShellArgs cmdArgs}
       '';
     };
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 3d41fe4013ea9..b5ebda6da045f 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -7,25 +7,20 @@ let
   opt = options.services.syncthing;
   defaultUser = "syncthing";
   defaultGroup = defaultUser;
+  settingsFormat = pkgs.formats.json { };
 
-  devices = mapAttrsToList (name: device: {
+  devices = mapAttrsToList (_: device: device // {
     deviceID = device.id;
-    inherit (device) name addresses introducer autoAcceptFolders;
-  }) cfg.devices;
-
-  folders = mapAttrsToList ( _: folder: {
-    inherit (folder) path id label type;
-    devices = map (device: { deviceId = cfg.devices.${device}.id; }) folder.devices;
-    rescanIntervalS = folder.rescanInterval;
-    fsWatcherEnabled = folder.watch;
-    fsWatcherDelayS = folder.watchDelay;
-    ignorePerms = folder.ignorePerms;
-    ignoreDelete = folder.ignoreDelete;
-    versioning = folder.versioning;
-  }) (filterAttrs (
-    _: folder:
-    folder.enable
-  ) cfg.folders);
+  }) cfg.settings.devices;
+
+  folders = mapAttrsToList (_: folder: folder // {
+    devices = map (device:
+      if builtins.isString device then
+        { deviceId = cfg.settings.devices.${device}.id; }
+      else
+        device
+    ) folder.devices;
+  }) cfg.settings.folders;
 
   updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
     set -efu
@@ -54,10 +49,10 @@ let
     old_cfg=$(curl ${cfg.guiAddress}/rest/config)
 
     # generate the new config by merging with the NixOS config options
-    new_cfg=$(printf '%s\n' "$old_cfg" | ${pkgs.jq}/bin/jq -c '. * {
-        "devices": (${builtins.toJSON devices}${optionalString (cfg.devices == {} || ! cfg.overrideDevices) " + .devices"}),
-        "folders": (${builtins.toJSON folders}${optionalString (cfg.folders == {} || ! cfg.overrideFolders) " + .folders"})
-    } * ${builtins.toJSON cfg.extraOptions}')
+    new_cfg=$(printf '%s\n' "$old_cfg" | ${pkgs.jq}/bin/jq -c ${escapeShellArg ''. * ${builtins.toJSON cfg.settings} * {
+        "devices": (${builtins.toJSON devices}${optionalString (cfg.settings.devices == {} || ! cfg.overrideDevices) " + .devices"}),
+        "folders": (${builtins.toJSON folders}${optionalString (cfg.settings.folders == {} || ! cfg.overrideFolders) " + .folders"})
+    }''})
 
     # send the new config
     curl -X PUT -d "$new_cfg" ${cfg.guiAddress}/rest/config
@@ -99,287 +94,282 @@ in {
         default = true;
         description = mdDoc ''
           Whether to delete the devices which are not configured via the
-          [devices](#opt-services.syncthing.devices) option.
+          [devices](#opt-services.syncthing.settings.devices) option.
           If set to `false`, devices added via the web
           interface will persist and will have to be deleted manually.
         '';
       };
 
-      devices = mkOption {
-        default = {};
-        description = mdDoc ''
-          Peers/devices which Syncthing should communicate with.
-
-          Note that you can still add devices manually, but those changes
-          will be reverted on restart if [overrideDevices](#opt-services.syncthing.overrideDevices)
-          is enabled.
-        '';
-        example = {
-          bigbox = {
-            id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
-            addresses = [ "tcp://192.168.0.10:51820" ];
-          };
-        };
-        type = types.attrsOf (types.submodule ({ name, ... }: {
-          options = {
-
-            name = mkOption {
-              type = types.str;
-              default = name;
-              description = lib.mdDoc ''
-                The name of the device.
-              '';
-            };
-
-            addresses = mkOption {
-              type = types.listOf types.str;
-              default = [];
-              description = lib.mdDoc ''
-                The addresses used to connect to the device.
-                If this is left empty, dynamic configuration is attempted.
-              '';
-            };
-
-            id = mkOption {
-              type = types.str;
-              description = mdDoc ''
-                The device ID. See <https://docs.syncthing.net/dev/device-ids.html>.
-              '';
-            };
-
-            introducer = mkOption {
-              type = types.bool;
-              default = false;
-              description = mdDoc ''
-                Whether the device should act as an introducer and be allowed
-                to add folders on this computer.
-                See <https://docs.syncthing.net/users/introducer.html>.
-              '';
-            };
-
-            autoAcceptFolders = mkOption {
-              type = types.bool;
-              default = false;
-              description = mdDoc ''
-                Automatically create or share folders that this device advertises at the default path.
-                See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>.
-              '';
-            };
-
-          };
-        }));
-      };
-
       overrideFolders = mkOption {
         type = types.bool;
         default = true;
         description = mdDoc ''
           Whether to delete the folders which are not configured via the
-          [folders](#opt-services.syncthing.folders) option.
+          [folders](#opt-services.syncthing.settings.folders) option.
           If set to `false`, folders added via the web
           interface will persist and will have to be deleted manually.
         '';
       };
 
-      folders = mkOption {
-        default = {};
-        description = mdDoc ''
-          Folders which should be shared by Syncthing.
-
-          Note that you can still add folders manually, but those changes
-          will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders)
-          is enabled.
-        '';
-        example = literalExpression ''
-          {
-            "/home/user/sync" = {
-              id = "syncme";
-              devices = [ "bigbox" ];
-            };
-          }
-        '';
-        type = types.attrsOf (types.submodule ({ name, ... }: {
+      settings = mkOption {
+        type = types.submodule {
+          freeformType = settingsFormat.type;
           options = {
-
-            enable = mkOption {
-              type = types.bool;
-              default = true;
-              description = lib.mdDoc ''
-                Whether to share this folder.
-                This option is useful when you want to define all folders
-                in one place, but not every machine should share all folders.
+            # global options
+            options = mkOption {
+              default = {};
+              description = mdDoc ''
+                The options element contains all other global configuration options
               '';
-            };
+              type = types.attrsOf (types.submodule ({ name, ... }: {
+                freeformType = settingsFormat.type;
+                options = {
 
-            path = mkOption {
-              # TODO for release 23.05: allow relative paths again and set
-              # working directory to cfg.dataDir
-              type = types.str // {
-                check = x: types.str.check x && (substring 0 1 x == "/" || substring 0 2 x == "~/");
-                description = types.str.description + " starting with / or ~/";
-              };
-              default = name;
-              description = lib.mdDoc ''
-                The path to the folder which should be shared.
-                Only absolute paths (starting with `/`) and paths relative to
-                the [user](#opt-services.syncthing.user)'s home directory
-                (starting with `~/`) are allowed.
-              '';
-            };
+                  localAnnounceEnabled = mkOption {
+                    type = types.bool;
+                    default = true;
+                    description = lib.mdDoc ''
+                      Whether to send announcements to the local LAN, also use such announcements to find other devices.
+                    '';
+                  };
 
-            id = mkOption {
-              type = types.str;
-              default = name;
-              description = lib.mdDoc ''
-                The ID of the folder. Must be the same on all devices.
-              '';
-            };
+                  localAnnouncePort = mkOption {
+                    type = types.int;
+                    default = 21027;
+                    description = lib.mdDoc ''
+                      The port on which to listen and send IPv4 broadcast announcements to.
+                    '';
+                  };
 
-            label = mkOption {
-              type = types.str;
-              default = name;
-              description = lib.mdDoc ''
-                The label of the folder.
-              '';
+                  relaysEnabled = mkOption {
+                    type = types.bool;
+                    default = true;
+                    description = lib.mdDoc ''
+                      When true, relays will be connected to and potentially used for device to device connections.
+                    '';
+                  };
+
+                  urAccepted = mkOption {
+                    type = types.int;
+                    default = 0;
+                    description = lib.mdDoc ''
+                      Whether the user has accepted to submit anonymous usage data.
+                      The default, 0, mean the user has not made a choice, and Syncthing will ask at some point in the future.
+                      "-1" means no, a number above zero means that that version of usage reporting has been accepted.
+                    '';
+                  };
+
+                  limitBandwidthInLan = mkOption {
+                    type = types.bool;
+                    default = false;
+                    description = lib.mdDoc ''
+                      Whether to apply bandwidth limits to devices in the same broadcast domain as the local device.
+                    '';
+                  };
+
+                  maxFolderConcurrency = mkOption {
+                    type = types.int;
+                    default = 0;
+                    description = lib.mdDoc ''
+                      This option controls how many folders may concurrently be in I/O-intensive operations such as syncing or scanning.
+                      The mechanism is described in detail in a [separate chapter](https://docs.syncthing.net/advanced/option-max-concurrency.html).
+                    '';
+                  };
+                };
+              }));
             };
 
+            # device settings
             devices = mkOption {
-              type = types.listOf types.str;
-              default = [];
+              default = {};
               description = mdDoc ''
-                The devices this folder should be shared with. Each device must
-                be defined in the [devices](#opt-services.syncthing.devices) option.
-              '';
-            };
+                Peers/devices which Syncthing should communicate with.
 
-            versioning = mkOption {
-              default = null;
-              description = mdDoc ''
-                How to keep changed/deleted files with Syncthing.
-                There are 4 different types of versioning with different parameters.
-                See <https://docs.syncthing.net/users/versioning.html>.
+                Note that you can still add devices manually, but those changes
+                will be reverted on restart if [overrideDevices](#opt-services.syncthing.overrideDevices)
+                is enabled.
               '';
-              example = literalExpression ''
-                [
-                  {
-                    versioning = {
-                      type = "simple";
-                      params.keep = "10";
-                    };
-                  }
-                  {
-                    versioning = {
-                      type = "trashcan";
-                      params.cleanoutDays = "1000";
-                    };
-                  }
-                  {
-                    versioning = {
-                      type = "staggered";
-                      fsPath = "/syncthing/backup";
-                      params = {
-                        cleanInterval = "3600";
-                        maxAge = "31536000";
-                      };
-                    };
-                  }
-                  {
-                    versioning = {
-                      type = "external";
-                      params.versionsPath = pkgs.writers.writeBash "backup" '''
-                        folderpath="$1"
-                        filepath="$2"
-                        rm -rf "$folderpath/$filepath"
-                      ''';
-                    };
-                  }
-                ]
-              '';
-              type = with types; nullOr (submodule {
+              example = {
+                bigbox = {
+                  id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+                  addresses = [ "tcp://192.168.0.10:51820" ];
+                };
+              };
+              type = types.attrsOf (types.submodule ({ name, ... }: {
+                freeformType = settingsFormat.type;
                 options = {
-                  type = mkOption {
-                    type = enum [ "external" "simple" "staggered" "trashcan" ];
-                    description = mdDoc ''
-                      The type of versioning.
-                      See <https://docs.syncthing.net/users/versioning.html>.
+
+                  name = mkOption {
+                    type = types.str;
+                    default = name;
+                    description = lib.mdDoc ''
+                      The name of the device.
                     '';
                   };
-                  fsPath = mkOption {
-                    default = "";
-                    type = either str path;
+
+                  id = mkOption {
+                    type = types.str;
                     description = mdDoc ''
-                      Path to the versioning folder.
-                      See <https://docs.syncthing.net/users/versioning.html>.
+                      The device ID. See <https://docs.syncthing.net/dev/device-ids.html>.
                     '';
                   };
-                  params = mkOption {
-                    type = attrsOf (either str path);
+
+                  autoAcceptFolders = mkOption {
+                    type = types.bool;
+                    default = false;
                     description = mdDoc ''
-                      The parameters for versioning. Structure depends on
-                      [versioning.type](#opt-services.syncthing.folders._name_.versioning.type).
-                      See <https://docs.syncthing.net/users/versioning.html>.
+                      Automatically create or share folders that this device advertises at the default path.
+                      See <https://docs.syncthing.net/users/config.html?highlight=autoaccept#config-file-format>.
                     '';
                   };
+
                 };
-              });
+              }));
             };
 
-            rescanInterval = mkOption {
-              type = types.int;
-              default = 3600;
-              description = lib.mdDoc ''
-                How often the folder should be rescanned for changes.
-              '';
-            };
+            # folder settings
+            folders = mkOption {
+              default = {};
+              description = mdDoc ''
+                Folders which should be shared by Syncthing.
 
-            type = mkOption {
-              type = types.enum [ "sendreceive" "sendonly" "receiveonly" "receiveencrypted" ];
-              default = "sendreceive";
-              description = lib.mdDoc ''
-                Whether to only send changes for this folder, only receive them
-                or both. `receiveencrypted` can be used for untrusted devices. See
-                <https://docs.syncthing.net/users/untrusted.html> for reference.
+                Note that you can still add folders manually, but those changes
+                will be reverted on restart if [overrideFolders](#opt-services.syncthing.overrideFolders)
+                is enabled.
               '';
-            };
-
-            watch = mkOption {
-              type = types.bool;
-              default = true;
-              description = lib.mdDoc ''
-                Whether the folder should be watched for changes by inotify.
+              example = literalExpression ''
+                {
+                  "/home/user/sync" = {
+                    id = "syncme";
+                    devices = [ "bigbox" ];
+                  };
+                }
               '';
-            };
+              type = types.attrsOf (types.submodule ({ name, ... }: {
+                freeformType = settingsFormat.type;
+                options = {
 
-            watchDelay = mkOption {
-              type = types.int;
-              default = 10;
-              description = lib.mdDoc ''
-                The delay after an inotify event is triggered.
-              '';
-            };
+                  enable = mkOption {
+                    type = types.bool;
+                    default = true;
+                    description = lib.mdDoc ''
+                      Whether to share this folder.
+                      This option is useful when you want to define all folders
+                      in one place, but not every machine should share all folders.
+                    '';
+                  };
 
-            ignorePerms = mkOption {
-              type = types.bool;
-              default = true;
-              description = lib.mdDoc ''
-                Whether to ignore permission changes.
-              '';
-            };
+                  path = mkOption {
+                    # TODO for release 23.05: allow relative paths again and set
+                    # working directory to cfg.dataDir
+                    type = types.str // {
+                      check = x: types.str.check x && (substring 0 1 x == "/" || substring 0 2 x == "~/");
+                      description = types.str.description + " starting with / or ~/";
+                    };
+                    default = name;
+                    description = lib.mdDoc ''
+                      The path to the folder which should be shared.
+                      Only absolute paths (starting with `/`) and paths relative to
+                      the [user](#opt-services.syncthing.user)'s home directory
+                      (starting with `~/`) are allowed.
+                    '';
+                  };
 
-            ignoreDelete = mkOption {
-              type = types.bool;
-              default = false;
-              description = mdDoc ''
-                Whether to skip deleting files that are deleted by peers.
-                See <https://docs.syncthing.net/advanced/folder-ignoredelete.html>.
-              '';
+                  id = mkOption {
+                    type = types.str;
+                    default = name;
+                    description = lib.mdDoc ''
+                      The ID of the folder. Must be the same on all devices.
+                    '';
+                  };
+
+                  label = mkOption {
+                    type = types.str;
+                    default = name;
+                    description = lib.mdDoc ''
+                      The label of the folder.
+                    '';
+                  };
+
+                  devices = mkOption {
+                    type = types.listOf types.str;
+                    default = [];
+                    description = mdDoc ''
+                      The devices this folder should be shared with. Each device must
+                      be defined in the [devices](#opt-services.syncthing.settings.devices) option.
+                    '';
+                  };
+
+                  versioning = mkOption {
+                    default = null;
+                    description = mdDoc ''
+                      How to keep changed/deleted files with Syncthing.
+                      There are 4 different types of versioning with different parameters.
+                      See <https://docs.syncthing.net/users/versioning.html>.
+                    '';
+                    example = literalExpression ''
+                      [
+                        {
+                          versioning = {
+                            type = "simple";
+                            params.keep = "10";
+                          };
+                        }
+                        {
+                          versioning = {
+                            type = "trashcan";
+                            params.cleanoutDays = "1000";
+                          };
+                        }
+                        {
+                          versioning = {
+                            type = "staggered";
+                            fsPath = "/syncthing/backup";
+                            params = {
+                              cleanInterval = "3600";
+                              maxAge = "31536000";
+                            };
+                          };
+                        }
+                        {
+                          versioning = {
+                            type = "external";
+                            params.versionsPath = pkgs.writers.writeBash "backup" '''
+                              folderpath="$1"
+                              filepath="$2"
+                              rm -rf "$folderpath/$filepath"
+                            ''';
+                          };
+                        }
+                      ]
+                    '';
+                    type = with types; nullOr (submodule {
+                      options = {
+                        type = mkOption {
+                          type = enum [ "external" "simple" "staggered" "trashcan" ];
+                          description = mdDoc ''
+                            The type of versioning.
+                            See <https://docs.syncthing.net/users/versioning.html>.
+                          '';
+                        };
+                      };
+                    });
+                  };
+
+                  copyOwnershipFromParent = mkOption {
+                    type = types.bool;
+                    default = false;
+                    description = mdDoc ''
+                      On Unix systems, tries to copy file/folder ownership from the parent directory (the directory it’s located in).
+                      Requires running Syncthing as a privileged user, or granting it additional capabilities (e.g. CAP_CHOWN on Linux).
+                    '';
+                  };
+                };
+              }));
             };
-          };
-        }));
-      };
 
-      extraOptions = mkOption {
-        type = types.addCheck (pkgs.formats.json {}).type isAttrs;
+          };
+        };
         default = {};
         description = mdDoc ''
           Extra configuration options for Syncthing.
@@ -530,6 +520,10 @@ in {
       This option was removed because Syncthing now has the inotify functionality included under the name "fswatcher".
       It can be enabled on a per-folder basis through the web interface.
     '')
+    (mkRenamedOptionModule [ "services" "syncthing" "extraOptions" ] [ "services" "syncthing" "settings" ])
+    (mkRenamedOptionModule [ "services" "syncthing" "folders" ] [ "services" "syncthing" "settings" "folders" ])
+    (mkRenamedOptionModule [ "services" "syncthing" "devices" ] [ "services" "syncthing" "settings" "devices" ])
+    (mkRenamedOptionModule [ "services" "syncthing" "options" ] [ "services" "syncthing" "settings" "options" ])
   ] ++ map (o:
     mkRenamedOptionModule [ "services" "syncthing" "declarative" o ] [ "services" "syncthing" o ]
   ) [ "cert" "key" "devices" "folders" "overrideDevices" "overrideFolders" "extraOptions"];
@@ -616,7 +610,7 @@ in {
         };
       };
       syncthing-init = mkIf (
-        cfg.devices != {} || cfg.folders != {} || cfg.extraOptions != {}
+        cfg.settings.devices != {} || cfg.folders != {} || cfg.extraOptions != {}
       ) {
         description = "Syncthing configuration updater";
         requisite = [ "syncthing.service" ];
diff --git a/nixos/modules/services/networking/wgautomesh.nix b/nixos/modules/services/networking/wgautomesh.nix
new file mode 100644
index 0000000000000..93227a9b625d0
--- /dev/null
+++ b/nixos/modules/services/networking/wgautomesh.nix
@@ -0,0 +1,161 @@
+{ lib, config, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.wgautomesh;
+  settingsFormat = pkgs.formats.toml { };
+  configFile =
+    # Have to remove nulls manually as TOML generator will not just skip key
+    # if value is null
+    settingsFormat.generate "wgautomesh-config.toml"
+      (filterAttrs (k: v: v != null)
+        (mapAttrs
+          (k: v:
+            if k == "peers"
+            then map (e: filterAttrs (k: v: v != null) e) v
+            else v)
+          cfg.settings));
+  runtimeConfigFile =
+    if cfg.enableGossipEncryption
+    then "/run/wgautomesh/wgautomesh.toml"
+    else configFile;
+in
+{
+  options.services.wgautomesh = {
+    enable = mkEnableOption (mdDoc "the wgautomesh daemon");
+    logLevel = mkOption {
+      type = types.enum [ "trace" "debug" "info" "warn" "error" ];
+      default = "info";
+      description = mdDoc "wgautomesh log level.";
+    };
+    enableGossipEncryption = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc "Enable encryption of gossip traffic.";
+    };
+    gossipSecretFile = mkOption {
+      type = types.path;
+      description = mdDoc ''
+        File containing the shared secret key to use for gossip encryption.
+        Required if `enableGossipEncryption` is set.
+      '';
+    };
+    enablePersistence = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc "Enable persistence of Wireguard peer info between restarts.";
+    };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = true;
+      description = mdDoc "Automatically open gossip port in firewall (recommended).";
+    };
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+
+          interface = mkOption {
+            type = types.str;
+            description = mdDoc ''
+              Wireguard interface to manage (it is NOT created by wgautomesh, you
+              should use another NixOS option to create it such as
+              `networking.wireguard.interfaces.wg0 = {...};`).
+            '';
+            example = "wg0";
+          };
+          gossip_port = mkOption {
+            type = types.port;
+            description = mdDoc ''
+              wgautomesh gossip port, this MUST be the same number on all nodes in
+              the wgautomesh network.
+            '';
+            default = 1666;
+          };
+          lan_discovery = mkOption {
+            type = types.bool;
+            default = true;
+            description = mdDoc "Enable discovery of peers on the same LAN using UDP broadcast.";
+          };
+          upnp_forward_external_port = mkOption {
+            type = types.nullOr types.port;
+            default = null;
+            description = mdDoc ''
+              Public port number to try to redirect to this machine's Wireguard
+              daemon using UPnP IGD.
+            '';
+          };
+          peers = mkOption {
+            type = types.listOf (types.submodule {
+              options = {
+                pubkey = mkOption {
+                  type = types.str;
+                  description = mdDoc "Wireguard public key of this peer.";
+                };
+                address = mkOption {
+                  type = types.str;
+                  description = mdDoc ''
+                    Wireguard address of this peer (a single IP address, multliple
+                    addresses or address ranges are not supported).
+                  '';
+                  example = "10.0.0.42";
+                };
+                endpoint = mkOption {
+                  type = types.nullOr types.str;
+                  description = mdDoc ''
+                    Bootstrap endpoint for connecting to this Wireguard peer if no
+                    other address is known or none are working.
+                  '';
+                  default = null;
+                  example = "wgnode.mydomain.example:51820";
+                };
+              };
+            });
+            default = [ ];
+            description = mdDoc "wgautomesh peer list.";
+          };
+        };
+
+      };
+      default = { };
+      description = mdDoc "Configuration for wgautomesh.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.wgautomesh.settings = {
+      gossip_secret_file = mkIf cfg.enableGossipEncryption "$CREDENTIALS_DIRECTORY/gossip_secret";
+      persist_file = mkIf cfg.enablePersistence "/var/lib/wgautomesh/state";
+    };
+
+    systemd.services.wgautomesh = {
+      path = [ pkgs.wireguard-tools ];
+      environment = { RUST_LOG = "wgautomesh=${cfg.logLevel}"; };
+      description = "wgautomesh";
+      serviceConfig = {
+        Type = "simple";
+
+        ExecStart = "${getExe pkgs.wgautomesh} ${runtimeConfigFile}";
+        Restart = "always";
+        RestartSec = "30";
+        LoadCredential = mkIf cfg.enableGossipEncryption [ "gossip_secret:${cfg.gossipSecretFile}" ];
+
+        ExecStartPre = mkIf cfg.enableGossipEncryption [
+          ''${pkgs.envsubst}/bin/envsubst \
+              -i ${configFile} \
+              -o ${runtimeConfigFile}''
+        ];
+
+        DynamicUser = true;
+        StateDirectory = "wgautomesh";
+        StateDirectoryMode = "0700";
+        RuntimeDirectory = "wgautomesh";
+        AmbientCapabilities = "CAP_NET_ADMIN";
+        CapabilityBoundingSet = "CAP_NET_ADMIN";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+    networking.firewall.allowedUDPPorts =
+      mkIf cfg.openFirewall [ cfg.settings.gossip_port ];
+  };
+}
+
diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix
index 440b617f60a39..067d5df487255 100644
--- a/nixos/modules/services/networking/wstunnel.nix
+++ b/nixos/modules/services/networking/wstunnel.nix
@@ -294,7 +294,7 @@ let
         DynamicUser = true;
         SupplementaryGroups = optional (serverCfg.useACMEHost != null) certConfig.group;
         PrivateTmp = true;
-        AmbientCapabilities = optional (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        AmbientCapabilities = optionals (serverCfg.listen.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
         NoNewPrivileges = true;
         RestrictNamespaces = "uts ipc pid user cgroup";
         ProtectSystem = "strict";
@@ -340,7 +340,7 @@ let
         EnvironmentFile = optional (clientCfg.environmentFile != null) clientCfg.environmentFile;
         DynamicUser = true;
         PrivateTmp = true;
-        AmbientCapabilities = (optional (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]) ++ (optional ((clientCfg.dynamicToRemote.port or 1024) < 1024 || (any (x: x.local.port < 1024) clientCfg.localToRemote)) [ "CAP_NET_BIND_SERVICE" ]);
+        AmbientCapabilities = (optionals (clientCfg.soMark != null) [ "CAP_NET_ADMIN" ]) ++ (optionals ((clientCfg.dynamicToRemote.port or 1024) < 1024 || (any (x: x.local.port < 1024) clientCfg.localToRemote)) [ "CAP_NET_BIND_SERVICE" ]);
         NoNewPrivileges = true;
         RestrictNamespaces = "uts ipc pid user cgroup";
         ProtectSystem = "strict";
diff --git a/nixos/modules/services/networking/xinetd.nix b/nixos/modules/services/networking/xinetd.nix
index b9120f37ba247..fb3de7077e31e 100644
--- a/nixos/modules/services/networking/xinetd.nix
+++ b/nixos/modules/services/networking/xinetd.nix
@@ -27,7 +27,7 @@ let
         ${optionalString srv.unlisted "type        = UNLISTED"}
         ${optionalString (srv.flags != "") "flags = ${srv.flags}"}
         socket_type = ${if srv.protocol == "udp" then "dgram" else "stream"}
-        ${if srv.port != 0 then "port        = ${toString srv.port}" else ""}
+        ${optionalString (srv.port != 0) "port        = ${toString srv.port}"}
         wait        = ${if srv.protocol == "udp" then "yes" else "no"}
         user        = ${srv.user}
         server      = ${srv.server}
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 9ac89e057620b..f6a23fb900f08 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -317,6 +317,7 @@ in
     environment.etc.cups.source = "/var/lib/cups";
 
     services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
+    services.udev.packages = cfg.drivers;
 
     # Allow asswordless printer admin for members of wheel group
     security.polkit.extraConfig = mkIf polkitEnabled ''
diff --git a/nixos/modules/services/security/authelia.nix b/nixos/modules/services/security/authelia.nix
index 143c441c7e153..28c5fd0a1df59 100644
--- a/nixos/modules/services/security/authelia.nix
+++ b/nixos/modules/services/security/authelia.nix
@@ -336,7 +336,7 @@ in
             ProtectProc = "noaccess";
             ProtectSystem = "strict";
 
-            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
             RestrictNamespaces = true;
             RestrictRealtime = true;
             RestrictSUIDSGID = true;
diff --git a/nixos/modules/services/security/fail2ban.nix b/nixos/modules/services/security/fail2ban.nix
index ead24d1470717..eebf2a2f7b55d 100644
--- a/nixos/modules/services/security/fail2ban.nix
+++ b/nixos/modules/services/security/fail2ban.nix
@@ -78,6 +78,13 @@ in
         '';
       };
 
+      bantime = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        example = "10m";
+        description = lib.mdDoc "Number of seconds that a host is banned.";
+      };
+
       maxretry = mkOption {
         default = 3;
         type = types.ints.unsigned;
@@ -111,56 +118,56 @@ in
         default = false;
         type = types.bool;
         description = lib.mdDoc ''
-          Allows to use database for searching of previously banned ip's to increase
-          a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32...
+          "bantime.increment" allows to use database for searching of previously banned ip's to increase
+          a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32 ...
         '';
       };
 
       bantime-increment.rndtime = mkOption {
-        default = "4m";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.str;
         example = "8m";
         description = lib.mdDoc ''
-          "bantime-increment.rndtime" is the max number of seconds using for mixing with random time
+          "bantime.rndtime" is the max number of seconds using for mixing with random time
           to prevent "clever" botnets calculate exact time IP can be unbanned again
         '';
       };
 
       bantime-increment.maxtime = mkOption {
-        default = "10h";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.str;
         example = "48h";
         description = lib.mdDoc ''
-          "bantime-increment.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
+          "bantime.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
         '';
       };
 
       bantime-increment.factor = mkOption {
-        default = "1";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.str;
         example = "4";
         description = lib.mdDoc ''
-          "bantime-increment.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
+          "bantime.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
           default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ...
         '';
       };
 
       bantime-increment.formula = mkOption {
-        default = "ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor";
-        type = types.str;
+        default = null;
+        type = types.nullOr types.str;
         example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
         description = lib.mdDoc ''
-          "bantime-increment.formula" used by default to calculate next value of ban time, default value bellow,
-          the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32...
+          "bantime.formula" used by default to calculate next value of ban time, default value bellow,
+          the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32 ...
         '';
       };
 
       bantime-increment.multipliers = mkOption {
-        default = "1 2 4 8 16 32 64";
-        type = types.str;
-        example = "2 4 16 128";
+        default = null;
+        type = types.nullOr types.str;
+        example = "1 2 4 8 16 32 64";
         description = lib.mdDoc ''
-          "bantime-increment.multipliers" used to calculate next value of ban time instead of formula, corresponding
+          "bantime.multipliers" used to calculate next value of ban time instead of formula, corresponding
           previously ban count and given "bantime.factor" (for multipliers default is 1);
           following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
           always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
@@ -168,11 +175,11 @@ in
       };
 
       bantime-increment.overalljails = mkOption {
-        default = false;
-        type = types.bool;
+        default = null;
+        type = types.nullOr types.bool;
         example = true;
         description = lib.mdDoc ''
-          "bantime-increment.overalljails"  (if true) specifies the search of IP in the database will be executed
+          "bantime.overalljails" (if true) specifies the search of IP in the database will be executed
           cross over all jails, if false (default), only current jail of the ban IP will be searched
         '';
       };
@@ -202,6 +209,20 @@ in
        '';
       };
 
+      extraSettings = mkOption {
+        type = with types; attrsOf (oneOf [ bool ints.positive str ]);
+        default = {};
+        description = lib.mdDoc ''
+          Extra default configuration for all jails (i.e. `[DEFAULT]`). See
+          <https://github.com/fail2ban/fail2ban/blob/master/config/jail.conf> for an overview.
+        '';
+        example = literalExpression ''
+          {
+            findtime = "15m";
+          }
+        '';
+      };
+
       jails = mkOption {
         default = { };
         example = literalExpression ''
@@ -255,8 +276,16 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.bantime-increment.formula == null || cfg.bantime-increment.multipliers == null);
+        message = ''
+          Options `services.fail2ban.bantime-increment.formula` and `services.fail2ban.bantime-increment.multipliers` cannot be both specified.
+        '';
+      }
+    ];
 
-    warnings = mkIf (config.networking.firewall.enable == false && config.networking.nftables.enable == false) [
+    warnings = mkIf (!config.networking.firewall.enable && !config.networking.nftables.enable) [
       "fail2ban can not be used without a firewall"
     ];
 
@@ -309,22 +338,28 @@ in
     # Add some reasonable default jails.  The special "DEFAULT" jail
     # sets default values for all other jails.
     services.fail2ban.jails.DEFAULT = ''
-      ${optionalString cfg.bantime-increment.enable ''
-        # Bantime incremental
-        bantime.increment    = ${boolToString cfg.bantime-increment.enable}
-        bantime.maxtime      = ${cfg.bantime-increment.maxtime}
-        bantime.factor       = ${cfg.bantime-increment.factor}
-        bantime.formula      = ${cfg.bantime-increment.formula}
-        bantime.multipliers  = ${cfg.bantime-increment.multipliers}
-        bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}
-      ''}
+      # Bantime increment options
+      bantime.increment = ${boolToString cfg.bantime-increment.enable}
+      ${optionalString (cfg.bantime-increment.rndtime != null) "bantime.rndtime = ${cfg.bantime-increment.rndtime}"}
+      ${optionalString (cfg.bantime-increment.maxtime != null) "bantime.maxtime = ${cfg.bantime-increment.maxtime}"}
+      ${optionalString (cfg.bantime-increment.factor != null) "bantime.factor = ${cfg.bantime-increment.factor}"}
+      ${optionalString (cfg.bantime-increment.formula != null) "bantime.formula = ${cfg.bantime-increment.formula}"}
+      ${optionalString (cfg.bantime-increment.multipliers != null) "bantime.multipliers = ${cfg.bantime-increment.multipliers}"}
+      ${optionalString (cfg.bantime-increment.overalljails != null) "bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}"}
       # Miscellaneous options
       ignoreip    = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}
+      ${optionalString (cfg.bantime != null) ''
+        bantime     = ${cfg.bantime}
+      ''}
       maxretry    = ${toString cfg.maxretry}
       backend     = systemd
       # Actions
       banaction   = ${cfg.banaction}
       banaction_allports = ${cfg.banaction-allports}
+      ${optionalString (cfg.extraSettings != {}) ''
+        # Extra settings
+        ${generators.toKeyValue {} cfg.extraSettings}
+      ''}
     '';
     # Block SSH if there are too many failing connection attempts.
     # Benefits from verbose sshd logging to observe failed login attempts,
diff --git a/nixos/modules/services/security/kanidm.nix b/nixos/modules/services/security/kanidm.nix
index 5583c39368f77..2f19decb5cb17 100644
--- a/nixos/modules/services/security/kanidm.nix
+++ b/nixos/modules/services/security/kanidm.nix
@@ -7,6 +7,18 @@ let
   serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings);
   clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings);
   unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings);
+  certPaths = builtins.map builtins.dirOf [ cfg.serverSettings.tls_chain cfg.serverSettings.tls_key ];
+
+  # Merge bind mount paths and remove paths where a prefix is already mounted.
+  # This makes sure that if e.g. the tls_chain is in the nix store and /nix/store is alread in the mount
+  # paths, no new bind mount is added. Adding subpaths caused problems on ofborg.
+  hasPrefixInList = list: newPath: lib.any (path: lib.hasPrefix (builtins.toString path) (builtins.toString newPath)) list;
+  mergePaths = lib.foldl' (merged: newPath: let
+      # If the new path is a prefix to some existing path, we need to filter it out
+      filteredPaths = lib.filter (p: !lib.hasPrefix (builtins.toString newPath) (builtins.toString p)) merged;
+      # If a prefix of the new path is already in the list, do not add it
+      filteredNew = if hasPrefixInList filteredPaths newPath then [] else [ newPath ];
+    in filteredPaths ++ filteredNew) [];
 
   defaultServiceConfig = {
     BindReadOnlyPaths = [
@@ -16,7 +28,7 @@ let
       "-/etc/hosts"
       "-/etc/localtime"
     ];
-    CapabilityBoundingSet = "";
+    CapabilityBoundingSet = [];
     # ProtectClock= adds DeviceAllow=char-rtc r
     DeviceAllow = "";
     # Implies ProtectSystem=strict, which re-mounts all paths
@@ -216,22 +228,28 @@ in
       description = "kanidm identity management daemon";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-      serviceConfig = defaultServiceConfig // {
-        StateDirectory = "kanidm";
-        StateDirectoryMode = "0700";
-        ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}";
-        User = "kanidm";
-        Group = "kanidm";
+      serviceConfig = lib.mkMerge [
+        # Merge paths and ignore existing prefixes needs to sidestep mkMerge
+        (defaultServiceConfig // {
+          BindReadOnlyPaths = mergePaths (defaultServiceConfig.BindReadOnlyPaths ++ certPaths);
+        })
+        {
+          StateDirectory = "kanidm";
+          StateDirectoryMode = "0700";
+          ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}";
+          User = "kanidm";
+          Group = "kanidm";
 
-        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
-        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
-        # This would otherwise override the CAP_NET_BIND_SERVICE capability.
-        PrivateUsers = false;
-        # Port needs to be exposed to the host network
-        PrivateNetwork = false;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
-        TemporaryFileSystem = "/:ro";
-      };
+          AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+          CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
+          # This would otherwise override the CAP_NET_BIND_SERVICE capability.
+          PrivateUsers = lib.mkForce false;
+          # Port needs to be exposed to the host network
+          PrivateNetwork = lib.mkForce false;
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+          TemporaryFileSystem = "/:ro";
+        }
+      ];
       environment.RUST_LOG = "info";
     };
 
@@ -240,34 +258,32 @@ in
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       restartTriggers = [ unixConfigFile clientConfigFile ];
-      serviceConfig = defaultServiceConfig // {
-        CacheDirectory = "kanidm-unixd";
-        CacheDirectoryMode = "0700";
-        RuntimeDirectory = "kanidm-unixd";
-        ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd";
-        User = "kanidm-unixd";
-        Group = "kanidm-unixd";
+      serviceConfig = lib.mkMerge [
+        defaultServiceConfig
+        {
+          CacheDirectory = "kanidm-unixd";
+          CacheDirectoryMode = "0700";
+          RuntimeDirectory = "kanidm-unixd";
+          ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd";
+          User = "kanidm-unixd";
+          Group = "kanidm-unixd";
 
-        BindReadOnlyPaths = [
-          "/nix/store"
-          "-/etc/resolv.conf"
-          "-/etc/nsswitch.conf"
-          "-/etc/hosts"
-          "-/etc/localtime"
-          "-/etc/kanidm"
-          "-/etc/static/kanidm"
-          "-/etc/ssl"
-          "-/etc/static/ssl"
-        ];
-        BindPaths = [
-          # To create the socket
-          "/run/kanidm-unixd:/var/run/kanidm-unixd"
-        ];
-        # Needs to connect to kanidmd
-        PrivateNetwork = false;
-        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
-        TemporaryFileSystem = "/:ro";
-      };
+          BindReadOnlyPaths = [
+            "-/etc/kanidm"
+            "-/etc/static/kanidm"
+            "-/etc/ssl"
+            "-/etc/static/ssl"
+          ];
+          BindPaths = [
+            # To create the socket
+            "/run/kanidm-unixd:/var/run/kanidm-unixd"
+          ];
+          # Needs to connect to kanidmd
+          PrivateNetwork = lib.mkForce false;
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
+          TemporaryFileSystem = "/:ro";
+        }
+      ];
       environment.RUST_LOG = "info";
     };
 
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index e3f8e75ca2476..12547acabfe05 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -72,15 +72,14 @@ let
   } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
 
   mapConfig = key: attr:
-  if attr != null && attr != [] then (
+  optionalString (attr != null && attr != []) (
     if isDerivation attr then mapConfig key (toString attr) else
     if (builtins.typeOf attr) == "set" then concatStringsSep " "
       (mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else
     if (builtins.typeOf attr) == "list" then concatMapStringsSep " " (mapConfig key) attr else
     if (builtins.typeOf attr) == "bool" then "--${key}=${boolToString attr}" else
     if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else
-    "--${key}=${toString attr}")
-    else "";
+    "--${key}=${toString attr}");
 
   configString = concatStringsSep " " (mapAttrsToList mapConfig allConfig);
 in
diff --git a/nixos/modules/services/security/vault-agent.nix b/nixos/modules/services/security/vault-agent.nix
new file mode 100644
index 0000000000000..244004f7c9131
--- /dev/null
+++ b/nixos/modules/services/security/vault-agent.nix
@@ -0,0 +1,128 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  format = pkgs.formats.json { };
+  commonOptions = { pkgName, flavour ? pkgName }: mkOption {
+    default = { };
+    description = mdDoc ''
+      Attribute set of ${flavour} instances.
+      Creates independent `${flavour}-''${name}.service` systemd units for each instance defined here.
+    '';
+    type = with types; attrsOf (submodule ({ name, ... }: {
+      options = {
+        enable = mkEnableOption (mdDoc "this ${flavour} instance") // { default = true; };
+
+        package = mkPackageOptionMD pkgs pkgName { };
+
+        user = mkOption {
+          type = types.str;
+          default = "root";
+          description = mdDoc ''
+            User under which this instance runs.
+          '';
+        };
+
+        group = mkOption {
+          type = types.str;
+          default = "root";
+          description = mdDoc ''
+            Group under which this instance runs.
+          '';
+        };
+
+        settings = mkOption {
+          type = types.submodule {
+            freeformType = format.type;
+
+            options = {
+              pid_file = mkOption {
+                default = "/run/${flavour}/${name}.pid";
+                type = types.str;
+                description = mdDoc ''
+                  Path to use for the pid file.
+                '';
+              };
+
+              template = mkOption {
+                default = [ ];
+                type = with types; listOf (attrsOf anything);
+                description =
+                  let upstreamDocs =
+                    if flavour == "vault-agent"
+                    then "https://developer.hashicorp.com/vault/docs/agent/template"
+                    else "https://github.com/hashicorp/consul-template/blob/main/docs/configuration.md#templates";
+                  in
+                  mdDoc ''
+                    Template section of ${flavour}.
+                    Refer to <${upstreamDocs}> for supported values.
+                  '';
+              };
+            };
+          };
+
+          default = { };
+
+          description =
+            let upstreamDocs =
+              if flavour == "vault-agent"
+              then "https://developer.hashicorp.com/vault/docs/agent#configuration-file-options"
+              else "https://github.com/hashicorp/consul-template/blob/main/docs/configuration.md#configuration-file";
+            in
+            mdDoc ''
+              Free-form settings written directly to the `config.json` file.
+              Refer to <${upstreamDocs}> for supported values.
+
+              ::: {.note}
+              Resulting format is JSON not HCL.
+              Refer to <https://www.hcl2json.com/> if you are unsure how to convert HCL options to JSON.
+              :::
+            '';
+        };
+      };
+    }));
+  };
+
+  createAgentInstance = { instance, name, flavour }:
+    let
+      configFile = format.generate "${name}.json" instance.settings;
+    in
+    mkIf (instance.enable) {
+      description = "${flavour} daemon - ${name}";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pkgs.getent ];
+      startLimitIntervalSec = 60;
+      startLimitBurst = 3;
+      serviceConfig = {
+        User = instance.user;
+        Group = instance.group;
+        RuntimeDirectory = flavour;
+        ExecStart = "${getExe instance.package} ${optionalString ((getName instance.package) == "vault") "agent"} -config ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        KillSignal = "SIGINT";
+        TimeoutStopSec = "30s";
+        Restart = "on-failure";
+      };
+    };
+in
+{
+  options = {
+    services.consul-template.instances = commonOptions { pkgName = "consul-template"; };
+    services.vault-agent.instances = commonOptions { pkgName = "vault"; flavour = "vault-agent"; };
+  };
+
+  config = mkMerge (map
+    (flavour:
+      let cfg = config.services.${flavour}; in
+      mkIf (cfg.instances != { }) {
+        systemd.services = mapAttrs'
+          (name: instance: nameValuePair "${flavour}-${name}" (createAgentInstance { inherit name instance flavour; }))
+          cfg.instances;
+      })
+    [ "consul-template" "vault-agent" ]);
+
+  meta.maintainers = with maintainers; [ indeednotjames tcheronneau ];
+}
+
diff --git a/nixos/modules/services/system/cachix-agent/default.nix b/nixos/modules/services/system/cachix-agent/default.nix
index 11769d4e3095f..06494ddb631af 100644
--- a/nixos/modules/services/system/cachix-agent/default.nix
+++ b/nixos/modules/services/system/cachix-agent/default.nix
@@ -72,7 +72,7 @@ in {
         EnvironmentFile = cfg.credentialsFile;
         ExecStart = ''
           ${cfg.package}/bin/cachix ${lib.optionalString cfg.verbose "--verbose"} ${lib.optionalString (cfg.host != null) "--host ${cfg.host}"} \
-            deploy agent ${cfg.name} ${if cfg.profile != null then cfg.profile else ""}
+            deploy agent ${cfg.name} ${optionalString (cfg.profile != null) cfg.profile}
         '';
       };
     };
diff --git a/nixos/modules/services/system/cachix-watch-store.nix b/nixos/modules/services/system/cachix-watch-store.nix
index 85e9509bcc82d..89157b460b9a4 100644
--- a/nixos/modules/services/system/cachix-watch-store.nix
+++ b/nixos/modules/services/system/cachix-watch-store.nix
@@ -62,7 +62,13 @@ in
       after = [ "network-online.target" ];
       path = [ config.nix.package ];
       wantedBy = [ "multi-user.target" ];
+      unitConfig = {
+        # allow to restart indefinitely
+        StartLimitIntervalSec = 0;
+      };
       serviceConfig = {
+        # don't put too much stress on the machine when restarting
+        RestartSec = 1;
         # we don't want to kill children processes as those are deployments
         KillMode = "process";
         Restart = "on-failure";
diff --git a/nixos/modules/services/system/cloud-init.nix b/nixos/modules/services/system/cloud-init.nix
index d75070dea4346..4cda06ed42481 100644
--- a/nixos/modules/services/system/cloud-init.nix
+++ b/nixos/modules/services/system/cloud-init.nix
@@ -2,17 +2,22 @@
 
 with lib;
 
-let cfg = config.services.cloud-init;
-    path = with pkgs; [
-      cloud-init
-      iproute2
-      nettools
-      openssh
-      shadow
-      util-linux
-    ] ++ optional cfg.btrfs.enable btrfs-progs
-      ++ optional cfg.ext4.enable e2fsprogs
-    ;
+let
+  cfg = config.services.cloud-init;
+  path = with pkgs; [
+    cloud-init
+    iproute2
+    nettools
+    openssh
+    shadow
+    util-linux
+    busybox
+  ]
+  ++ optional cfg.btrfs.enable btrfs-progs
+  ++ optional cfg.ext4.enable e2fsprogs
+  ;
+  settingsFormat = pkgs.formats.yaml { };
+  cfgfile = settingsFormat.generate "cloud.cfg" cfg.settings;
 in
 {
   options = {
@@ -20,7 +25,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc ''
+        description = mdDoc ''
           Enable the cloud-init service. This services reads
           configuration metadata in a cloud environment and configures
           the machine according to this metadata.
@@ -39,7 +44,7 @@ in
       btrfs.enable = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc ''
+        description = mdDoc ''
           Allow the cloud-init service to operate `btrfs` filesystem.
         '';
       };
@@ -47,7 +52,7 @@ in
       ext4.enable = mkOption {
         type = types.bool;
         default = true;
-        description = lib.mdDoc ''
+        description = mdDoc ''
           Allow the cloud-init service to operate `ext4` filesystem.
         '';
       };
@@ -55,62 +60,30 @@ in
       network.enable = mkOption {
         type = types.bool;
         default = false;
-        description = lib.mdDoc ''
+        description = mdDoc ''
           Allow the cloud-init service to configure network interfaces
           through systemd-networkd.
         '';
       };
 
+      settings = mkOption {
+        description = mdDoc ''
+          Structured cloud-init configuration.
+        '';
+        type = types.submodule {
+          freeformType = settingsFormat.type;
+        };
+        default = { };
+      };
+
       config = mkOption {
         type = types.str;
-        default = ''
-          system_info:
-            distro: nixos
-            network:
-              renderers: [ 'networkd' ]
-          users:
-             - root
-
-          disable_root: false
-          preserve_hostname: false
-
-          cloud_init_modules:
-           - migrator
-           - seed_random
-           - bootcmd
-           - write-files
-           - growpart
-           - resizefs
-           - update_hostname
-           - resolv_conf
-           - ca-certs
-           - rsyslog
-           - users-groups
-
-          cloud_config_modules:
-           - disk_setup
-           - mounts
-           - ssh-import-id
-           - set-passwords
-           - timezone
-           - disable-ec2-metadata
-           - runcmd
-           - ssh
-
-          cloud_final_modules:
-           - rightscale_userdata
-           - scripts-vendor
-           - scripts-per-once
-           - scripts-per-boot
-           - scripts-per-instance
-           - scripts-user
-           - ssh-authkey-fingerprints
-           - keys-to-console
-           - phone-home
-           - final-message
-           - power-state-change
-          '';
-        description = lib.mdDoc "cloud-init configuration.";
+        default = "";
+        description = mdDoc ''
+          raw cloud-init configuration.
+
+          Takes precedence over the `settings` option if set.
+        '';
       };
 
     };
@@ -118,78 +91,140 @@ in
   };
 
   config = mkIf cfg.enable {
+    services.cloud-init.settings = {
+      system_info = mkDefault {
+        distro = "nixos";
+        network = {
+          renderers = [ "networkd" ];
+        };
+      };
 
-    environment.etc."cloud/cloud.cfg".text = cfg.config;
+      users = mkDefault [ "root" ];
+      disable_root = mkDefault false;
+      preserve_hostname = mkDefault false;
+
+      cloud_init_modules = mkDefault [
+        "migrator"
+        "seed_random"
+        "bootcmd"
+        "write-files"
+        "growpart"
+        "resizefs"
+        "update_hostname"
+        "resolv_conf"
+        "ca-certs"
+        "rsyslog"
+        "users-groups"
+      ];
+
+      cloud_config_modules = mkDefault [
+        "disk_setup"
+        "mounts"
+        "ssh-import-id"
+        "set-passwords"
+        "timezone"
+        "disable-ec2-metadata"
+        "runcmd"
+        "ssh"
+      ];
+
+      cloud_final_modules = mkDefault [
+        "rightscale_userdata"
+        "scripts-vendor"
+        "scripts-per-once"
+        "scripts-per-boot"
+        "scripts-per-instance"
+        "scripts-user"
+        "ssh-authkey-fingerprints"
+        "keys-to-console"
+        "phone-home"
+        "final-message"
+        "power-state-change"
+      ];
+    };
+
+    environment.etc."cloud/cloud.cfg" =
+      if cfg.config == "" then
+        { source = cfgfile; }
+      else
+        { text = cfg.config; }
+    ;
 
     systemd.network.enable = cfg.network.enable;
 
-    systemd.services.cloud-init-local =
-      { description = "Initial cloud-init job (pre-networking)";
-        wantedBy = [ "multi-user.target" ];
-        before = ["systemd-networkd.service"];
-        path = path;
-        serviceConfig =
-          { Type = "oneshot";
-            ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
-            RemainAfterExit = "yes";
-            TimeoutSec = "infinity";
-            StandardOutput = "journal+console";
-          };
+    systemd.services.cloud-init-local = {
+      description = "Initial cloud-init job (pre-networking)";
+      wantedBy = [ "multi-user.target" ];
+      before = [ "systemd-networkd.service" ];
+      path = path;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
+        RemainAfterExit = "yes";
+        TimeoutSec = "infinity";
+        StandardOutput = "journal+console";
       };
+    };
 
-    systemd.services.cloud-init =
-      { description = "Initial cloud-init job (metadata service crawler)";
-        wantedBy = [ "multi-user.target" ];
-        wants = [ "network-online.target" "cloud-init-local.service"
-                  "sshd.service" "sshd-keygen.service" ];
-        after = [ "network-online.target" "cloud-init-local.service" ];
-        before = [ "sshd.service" "sshd-keygen.service" ];
-        requires = [ "network.target"];
-        path = path;
-        serviceConfig =
-          { Type = "oneshot";
-            ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
-            RemainAfterExit = "yes";
-            TimeoutSec = "infinity";
-            StandardOutput = "journal+console";
-          };
+    systemd.services.cloud-init = {
+      description = "Initial cloud-init job (metadata service crawler)";
+      wantedBy = [ "multi-user.target" ];
+      wants = [
+        "network-online.target"
+        "cloud-init-local.service"
+        "sshd.service"
+        "sshd-keygen.service"
+      ];
+      after = [ "network-online.target" "cloud-init-local.service" ];
+      before = [ "sshd.service" "sshd-keygen.service" ];
+      requires = [ "network.target" ];
+      path = path;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
+        RemainAfterExit = "yes";
+        TimeoutSec = "infinity";
+        StandardOutput = "journal+console";
       };
+    };
 
-    systemd.services.cloud-config =
-      { description = "Apply the settings specified in cloud-config";
-        wantedBy = [ "multi-user.target" ];
-        wants = [ "network-online.target" ];
-        after = [ "network-online.target" "syslog.target" "cloud-config.target" ];
-
-        path = path;
-        serviceConfig =
-          { Type = "oneshot";
-            ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
-            RemainAfterExit = "yes";
-            TimeoutSec = "infinity";
-            StandardOutput = "journal+console";
-          };
+    systemd.services.cloud-config = {
+      description = "Apply the settings specified in cloud-config";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" "syslog.target" "cloud-config.target" ];
+
+      path = path;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
+        RemainAfterExit = "yes";
+        TimeoutSec = "infinity";
+        StandardOutput = "journal+console";
       };
+    };
 
-    systemd.services.cloud-final =
-      { description = "Execute cloud user/final scripts";
-        wantedBy = [ "multi-user.target" ];
-        wants = [ "network-online.target" ];
-        after = [ "network-online.target" "syslog.target" "cloud-config.service" "rc-local.service" ];
-        requires = [ "cloud-config.target" ];
-        path = path;
-        serviceConfig =
-          { Type = "oneshot";
-            ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
-            RemainAfterExit = "yes";
-            TimeoutSec = "infinity";
-            StandardOutput = "journal+console";
-          };
+    systemd.services.cloud-final = {
+      description = "Execute cloud user/final scripts";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" "syslog.target" "cloud-config.service" "rc-local.service" ];
+      requires = [ "cloud-config.target" ];
+      path = path;
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
+        RemainAfterExit = "yes";
+        TimeoutSec = "infinity";
+        StandardOutput = "journal+console";
       };
+    };
 
-    systemd.targets.cloud-config =
-      { description = "Cloud-config availability";
-        requires = [ "cloud-init-local.service" "cloud-init.service" ];
-      };
+    systemd.targets.cloud-config = {
+      description = "Cloud-config availability";
+      requires = [ "cloud-init-local.service" "cloud-init.service" ];
+    };
   };
+
+  meta.maintainers = [ maintainers.zimbatm ];
 }
diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix
index c677088101f0c..9d8a62ec78c53 100644
--- a/nixos/modules/services/system/dbus.nix
+++ b/nixos/modules/services/system/dbus.nix
@@ -14,13 +14,17 @@ let
     serviceDirectories = cfg.packages;
   };
 
-  inherit (lib) mkOption mkIf mkMerge types;
+  inherit (lib) mkOption mkEnableOption mkIf mkMerge types;
 
 in
 
 {
   options = {
 
+    boot.initrd.systemd.dbus = {
+      enable = mkEnableOption (lib.mdDoc "dbus in stage 1") // { visible = false; };
+    };
+
     services.dbus = {
 
       enable = mkOption {
@@ -111,6 +115,21 @@ in
       ];
     }
 
+    (mkIf config.boot.initrd.systemd.dbus.enable {
+      boot.initrd.systemd = {
+        users.messagebus = { };
+        groups.messagebus = { };
+        contents."/etc/dbus-1".source = pkgs.makeDBusConf {
+          inherit (cfg) apparmor;
+          suidHelper = "/bin/false";
+          serviceDirectories = [ pkgs.dbus ];
+        };
+        packages = [ pkgs.dbus ];
+        storePaths = [ "${pkgs.dbus}/bin/dbus-daemon" ];
+        targets.sockets.wants = [ "dbus.socket" ];
+      };
+    })
+
     (mkIf (cfg.implementation == "dbus") {
       environment.systemPackages = [
         pkgs.dbus
diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix
index de3d077daec96..003f7b2613b76 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -93,7 +93,7 @@ in {
             `true`.
 
             It does NOT apply to the daemon port nor the web UI port. To access those
-            ports secuerly check the documentation
+            ports securely check the documentation
             <https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel>
             or use a VPN or configure certificates for deluge.
           '';
diff --git a/nixos/modules/services/web-apps/discourse.nix b/nixos/modules/services/web-apps/discourse.nix
index 151fb812ddea6..f80eb6b4c7f02 100644
--- a/nixos/modules/services/web-apps/discourse.nix
+++ b/nixos/modules/services/web-apps/discourse.nix
@@ -651,6 +651,9 @@ in
       preload_link_header = false;
       redirect_avatar_requests = false;
       pg_force_readonly_mode = false;
+      dns_query_timeout_secs = null;
+      regex_timeout_seconds = 2;
+      allow_impersonation = true;
     };
 
     services.redis.servers.discourse =
@@ -1025,8 +1028,8 @@ in
 
     services.postfix = lib.mkIf cfg.mail.incoming.enable {
       enable = true;
-      sslCert = if cfg.sslCertificate != null then cfg.sslCertificate else "";
-      sslKey = if cfg.sslCertificateKey != null then cfg.sslCertificateKey else "";
+      sslCert = lib.optionalString (cfg.sslCertificate != null) cfg.sslCertificate;
+      sslKey = lib.optionalString (cfg.sslCertificateKey != null) cfg.sslCertificateKey;
 
       origin = cfg.hostname;
       relayDomains = [ cfg.hostname ];
diff --git a/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
index c85109b1479ed..3a66763b583ee 100644
--- a/nixos/modules/services/web-apps/dokuwiki.nix
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -3,6 +3,8 @@
 with lib;
 
 let
+  inherit (lib.options) showOption showFiles;
+
   cfg = config.services.dokuwiki;
   eachSite = cfg.sites;
   user = "dokuwiki";
@@ -63,7 +65,6 @@ let
     conf_gen = c: map (v: "$conf${v}") (mkPhpAttrVals c);
   in writePhpFile "local-${hostName}.php" ''
     ${concatStringsSep "\n" (conf_gen cfg.mergedConfig)}
-    ${toString cfg.extraConfig}
   '';
 
   dokuwikiPluginsLocalConfig = hostName: cfg: let
@@ -90,13 +91,13 @@ let
 
       page = mkOption {
         type = types.str;
-        description = "Page or namespace to restrict";
+        description = lib.mdDoc "Page or namespace to restrict";
         example = "start";
       };
 
       actor = mkOption {
         type = types.str;
-        description = "User or group to restrict";
+        description = lib.mdDoc "User or group to restrict";
         example = "@external";
       };
 
@@ -112,38 +113,69 @@ let
       in mkOption {
         type = types.enum ((attrValues available) ++ (attrNames available));
         apply = x: if isInt x then x else available.${x};
-        description = ''
+        description = lib.mdDoc ''
           Permission level to restrict the actor(s) to.
           See <https://www.dokuwiki.org/acl#background_info> for explanation
         '';
         example = "read";
       };
-
     };
   };
 
-  siteOpts = { config, lib, name, ... }:
+  # 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 = [
-        # NOTE: These will sadly not print the absolute argument path but only the name. Related to #96006
-        (mkRenamedOptionModule [ "aclUse" ] [ "settings" "useacl" ] )
-        (mkRenamedOptionModule [ "superUser" ] [ "settings" "superuser" ] )
-        (mkRenamedOptionModule [ "disableActions" ] [ "settings" "disableactions" ] )
-        ({ config, options, name, ...}: {
-          config.warnings =
-            (optional (isString config.pluginsConfig) ''
-              Passing plain strings to services.dokuwiki.sites.${name}.pluginsConfig has been deprecated and will not be continue to be supported in the future.
-              Please pass structured settings instead.
-            '')
-            ++ (optional (isString config.acl) ''
-              Passing a plain string to services.dokuwiki.sites.${name}.acl has been deprecated and will not continue to be supported in the future.
-              Please pass structured settings instead.
-            '')
-            ++ (optional (config.extraConfig != null) ''
-              services.dokuwiki.sites.${name}.extraConfig is deprecated and will be removed in the future.
-              Please pass structured settings to services.dokuwiki.sites.${name}.settings instead.
-            '')
-          ;
+        (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 defiend as `${config.usersFile}' in ${showFiles options.usersFile.files}).";
+            }
+          ];
         })
       ];
 
@@ -164,7 +196,7 @@ let
         };
 
         acl = mkOption {
-          type = with types; nullOr (oneOf [ lines (listOf (submodule aclOpts)) ]);
+          type = with types; nullOr (listOf (submodule aclOpts));
           default = null;
           example = literalExpression ''
             [
@@ -203,7 +235,7 @@ let
         };
 
         pluginsConfig = mkOption {
-          type = with types; oneOf [lines (attrsOf bool)];
+          type = with types; attrsOf bool;
           default = {
             authad = false;
             authldap = false;
@@ -370,36 +402,21 @@ let
           '';
         };
 
-        extraConfig = mkOption {
-          # This Option is deprecated and only kept until sometime before 23.05 for compatibility reasons
-          # FIXME (@e1mo): Actually remember removing this before 23.05.
-          visible = false;
-          type = types.nullOr types.lines;
-          default = null;
-          example = ''
-            $conf['title'] = 'My Wiki';
-            $conf['userewrite'] = 1;
-          '';
-          description = lib.mdDoc ''
-            DokuWiki configuration. Refer to
-            <https://www.dokuwiki.org/config>
-            for details on supported values.
-
-            **Note**: Please pass Structured settings via
-            `services.dokuwiki.sites.${name}.settings` instead.
-          '';
-        };
-
       # Required for the mkRenamedOptionModule
       # TODO: Remove me once https://github.com/NixOS/nixpkgs/issues/96006 is fixed
-      # or the aclUse, ... options are removed.
+      # 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
@@ -435,16 +452,7 @@ in
 
     warnings = flatten (mapAttrsToList (_: cfg: cfg.warnings) eachSite);
 
-    assertions = flatten (mapAttrsToList (hostName: cfg:
-    [{
-      assertion = cfg.mergedConfig.useacl -> (cfg.acl != null || cfg.aclFile != null);
-      message = "Either services.dokuwiki.sites.${hostName}.acl or services.dokuwiki.sites.${hostName}.aclFile is mandatory if settings.useacl is true";
-    }
-    {
-      assertion = cfg.usersFile != null -> cfg.mergedConfig.useacl != false;
-      message = "services.dokuwiki.sites.${hostName}.settings.useacl must must be true if usersFile is not null";
-    }
-    ]) eachSite);
+    assertions = flatten (mapAttrsToList (_: cfg: cfg.assertions) eachSite);
 
     services.phpfpm.pools = mapAttrs' (hostName: cfg: (
       nameValuePair "dokuwiki-${hostName}" {
diff --git a/nixos/modules/services/web-apps/kavita.nix b/nixos/modules/services/web-apps/kavita.nix
new file mode 100644
index 0000000000000..e28b204f1bbe7
--- /dev/null
+++ b/nixos/modules/services/web-apps/kavita.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.kavita;
+in {
+  options.services.kavita = {
+    enable = lib.mkEnableOption (lib.mdDoc "Kavita reading server");
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "kavita";
+      description = lib.mdDoc "User account under which Kavita runs.";
+    };
+
+    package = lib.mkPackageOptionMD pkgs "kavita" { };
+
+    dataDir = lib.mkOption {
+      default = "/var/lib/kavita";
+      type = lib.types.str;
+      description = lib.mdDoc "The directory where Kavita stores its state.";
+    };
+
+    tokenKeyFile = lib.mkOption {
+      type = lib.types.path;
+      description = lib.mdDoc ''
+        A file containing the TokenKey, a secret with at 128+ bits.
+        It can be generated with `head -c 32 /dev/urandom | base64`.
+      '';
+    };
+    port = lib.mkOption {
+      default = 5000;
+      type = lib.types.port;
+      description = lib.mdDoc "Port to bind to.";
+    };
+    ipAdresses = lib.mkOption {
+      default = ["0.0.0.0" "::"];
+      type = lib.types.listOf lib.types.str;
+      description = lib.mdDoc "IP Adresses to bind to. The default is to bind
+      to all IPv4 and IPv6 addresses.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.kavita = {
+      description = "Kavita";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      preStart = ''
+        umask u=rwx,g=rx,o=
+        cat > "${cfg.dataDir}/config/appsettings.json" <<EOF
+        {
+          "TokenKey": "$(cat ${cfg.tokenKeyFile})",
+          "Port": ${toString cfg.port},
+          "IpAddresses": "${lib.concatStringsSep "," cfg.ipAdresses}"
+        }
+        EOF
+      '';
+      serviceConfig = {
+        WorkingDirectory = cfg.dataDir;
+        ExecStart = "${lib.getExe cfg.package}";
+        Restart = "always";
+        User = cfg.user;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}'        0750 ${cfg.user} ${cfg.user} - -"
+      "d '${cfg.dataDir}/config' 0750 ${cfg.user} ${cfg.user} - -"
+    ];
+
+    users = {
+      users.${cfg.user} = {
+        description = "kavita service user";
+        isSystemUser = true;
+        group = cfg.user;
+        home = cfg.dataDir;
+      };
+      groups.${cfg.user} = { };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ misterio77 ];
+}
diff --git a/nixos/modules/services/web-apps/lemmy.nix b/nixos/modules/services/web-apps/lemmy.nix
index af0fb38121a34..f48afd98a6c97 100644
--- a/nixos/modules/services/web-apps/lemmy.nix
+++ b/nixos/modules/services/web-apps/lemmy.nix
@@ -156,8 +156,8 @@ in
         };
 
         documentation = [
-          "https://join-lemmy.org/docs/en/administration/from_scratch.html"
-          "https://join-lemmy.org/docs"
+          "https://join-lemmy.org/docs/en/admins/from_scratch.html"
+          "https://join-lemmy.org/docs/en/"
         ];
 
         wantedBy = [ "multi-user.target" ];
@@ -185,8 +185,8 @@ in
         };
 
         documentation = [
-          "https://join-lemmy.org/docs/en/administration/from_scratch.html"
-          "https://join-lemmy.org/docs"
+          "https://join-lemmy.org/docs/en/admins/from_scratch.html"
+          "https://join-lemmy.org/docs/en/"
         ];
 
         wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/web-apps/mainsail.nix b/nixos/modules/services/web-apps/mainsail.nix
new file mode 100644
index 0000000000000..f335d9b015d49
--- /dev/null
+++ b/nixos/modules/services/web-apps/mainsail.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.mainsail;
+  moonraker = config.services.moonraker;
+in
+{
+  options.services.mainsail = {
+    enable = mkEnableOption (lib.mdDoc "a modern and responsive user interface for Klipper");
+
+    package = mkOption {
+      type = types.package;
+      description = lib.mdDoc "Mainsail package to be used in the module";
+      default = pkgs.mainsail;
+      defaultText = literalExpression "pkgs.mainsail";
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = lib.mdDoc "Hostname to serve mainsail on";
+    };
+
+    nginx = mkOption {
+      type = types.submodule
+        (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
+      default = { };
+      example = literalExpression ''
+        {
+          serverAliases = [ "mainsail.''${config.networking.domain}" ];
+        }
+      '';
+      description = lib.mdDoc "Extra configuration for the nginx virtual host of mainsail.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.nginx = {
+      enable = true;
+      upstreams.mainsail-apiserver.servers."${moonraker.address}:${toString moonraker.port}" = { };
+      virtualHosts."${cfg.hostName}" = mkMerge [
+        cfg.nginx
+        {
+          root = mkForce "${cfg.package}/share/mainsail";
+          locations = {
+            "/" = {
+              index = "index.html";
+              tryFiles = "$uri $uri/ /index.html";
+            };
+            "/index.html".extraConfig = ''
+              add_header Cache-Control "no-store, no-cache, must-revalidate";
+            '';
+            "/websocket" = {
+              proxyWebsockets = true;
+              proxyPass = "http://mainsail-apiserver/websocket";
+            };
+            "~ ^/(printer|api|access|machine|server)/" = {
+              proxyWebsockets = true;
+              proxyPass = "http://mainsail-apiserver$request_uri";
+            };
+          };
+        }
+      ];
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/matomo.md b/nixos/modules/services/web-apps/matomo.md
index f5536a35f7a89..e750c0c147752 100644
--- a/nixos/modules/services/web-apps/matomo.md
+++ b/nixos/modules/services/web-apps/matomo.md
@@ -3,7 +3,7 @@
 Matomo is a real-time web analytics application. This module configures
 php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
 
-An automatic setup is not suported by Matomo, so you need to configure Matomo
+An automatic setup is not supported by Matomo, so you need to configure Matomo
 itself in the browser-based Matomo setup.
 
 ## Database Setup {#module-services-matomo-database-setup}
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index 357c2d4a12830..b912d4e87f18a 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -8,7 +8,8 @@ let
   cfg = config.services.mediawiki;
   fpm = config.services.phpfpm.pools.mediawiki;
   user = "mediawiki";
-  group = config.services.httpd.group;
+  group = if cfg.webserver == "apache" then "apache" else "mediawiki";
+
   cacheDir = "/var/cache/mediawiki";
   stateDir = "/var/lib/mediawiki";
 
@@ -73,7 +74,7 @@ let
       $wgScriptPath = "";
 
       ## The protocol and server name to use in fully-qualified URLs
-      $wgServer = "${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}";
+      $wgServer = "${cfg.url}";
 
       ## The URL path to static resources (images, scripts, etc.)
       $wgResourceBasePath = $wgScriptPath;
@@ -87,8 +88,7 @@ let
       $wgEnableEmail = true;
       $wgEnableUserEmail = true; # UPO
 
-      $wgEmergencyContact = "${if cfg.virtualHost.adminAddr != null then cfg.virtualHost.adminAddr else config.services.httpd.adminAddr}";
-      $wgPasswordSender = $wgEmergencyContact;
+      $wgPasswordSender = "${cfg.passwordSender}";
 
       $wgEnotifUserTalk = false; # UPO
       $wgEnotifWatchlist = false; # UPO
@@ -190,6 +190,16 @@ in
         description = lib.mdDoc "Which MediaWiki package to use.";
       };
 
+      finalPackage = mkOption {
+        type = types.package;
+        readOnly = true;
+        default = pkg;
+        defaultText = literalExpression "pkg";
+        description = lib.mdDoc ''
+          The final package used by the module. This is the package that will have extensions and skins installed.
+        '';
+      };
+
       name = mkOption {
         type = types.str;
         default = "MediaWiki";
@@ -197,6 +207,22 @@ in
         description = lib.mdDoc "Name of the wiki.";
       };
 
+      url = mkOption {
+        type = types.str;
+        default = if cfg.webserver == "apache" then
+            "${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://${cfg.httpd.virtualHost.hostName}"
+          else
+            "http://localhost";
+        defaultText = literalExpression ''
+          if cfg.webserver == "apache" then
+            "''${if cfg.httpd.virtualHost.addSSL || cfg.httpd.virtualHost.forceSSL || cfg.httpd.virtualHost.onlySSL then "https" else "http"}://''${cfg.httpd.virtualHost.hostName}"
+          else
+            "http://localhost";
+        '';
+        example = "https://wiki.example.org";
+        description = lib.mdDoc "URL of the wiki.";
+      };
+
       uploadsDir = mkOption {
         type = types.nullOr types.path;
         default = "${stateDir}/uploads";
@@ -212,6 +238,24 @@ in
         example = "/run/keys/mediawiki-password";
       };
 
+      passwordSender = mkOption {
+        type = types.str;
+        default =
+          if cfg.webserver == "apache" then
+            if cfg.httpd.virtualHost.adminAddr != null then
+              cfg.httpd.virtualHost.adminAddr
+            else
+              config.services.httpd.adminAddr else "root@localhost";
+        defaultText = literalExpression ''
+          if cfg.webserver == "apache" then
+            if cfg.httpd.virtualHost.adminAddr != null then
+              cfg.httpd.virtualHost.adminAddr
+            else
+              config.services.httpd.adminAddr else "root@localhost"
+        '';
+        description = lib.mdDoc "Contact address for password reset.";
+      };
+
       skins = mkOption {
         default = {};
         type = types.attrsOf types.path;
@@ -241,6 +285,12 @@ in
         '';
       };
 
+      webserver = mkOption {
+        type = types.enum [ "apache" "none" ];
+        default = "apache";
+        description = lib.mdDoc "Webserver to use.";
+      };
+
       database = {
         type = mkOption {
           type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ];
@@ -318,7 +368,7 @@ in
         };
       };
 
-      virtualHost = mkOption {
+      httpd.virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
         example = literalExpression ''
           {
@@ -366,6 +416,10 @@ in
     };
   };
 
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "mediawiki" "virtualHost" ] [ "services" "mediawiki" "httpd" "virtualHost" ])
+  ];
+
   # implementation
   config = mkIf cfg.enable {
 
@@ -412,36 +466,42 @@ in
     services.phpfpm.pools.mediawiki = {
       inherit user group;
       phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}";
-      settings = {
+      settings = (if (cfg.webserver == "apache") then {
         "listen.owner" = config.services.httpd.user;
         "listen.group" = config.services.httpd.group;
-      } // cfg.poolConfig;
+      } else {
+        "listen.owner" = user;
+        "listen.group" = group;
+      }) // cfg.poolConfig;
     };
 
-    services.httpd = {
+    services.httpd = lib.mkIf (cfg.webserver == "apache") {
       enable = true;
       extraModules = [ "proxy_fcgi" ];
-      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
-        documentRoot = mkForce "${pkg}/share/mediawiki";
-        extraConfig = ''
-          <Directory "${pkg}/share/mediawiki">
-            <FilesMatch "\.php$">
-              <If "-f %{REQUEST_FILENAME}">
-                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
-              </If>
-            </FilesMatch>
-
-            Require all granted
-            DirectoryIndex index.php
-            AllowOverride All
-          </Directory>
-        '' + optionalString (cfg.uploadsDir != null) ''
-          Alias "/images" "${cfg.uploadsDir}"
-          <Directory "${cfg.uploadsDir}">
-            Require all granted
-          </Directory>
-        '';
-      } ];
+      virtualHosts.${cfg.httpd.virtualHost.hostName} = mkMerge [
+        cfg.httpd.virtualHost
+        {
+          documentRoot = mkForce "${pkg}/share/mediawiki";
+          extraConfig = ''
+            <Directory "${pkg}/share/mediawiki">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+                </If>
+              </FilesMatch>
+
+              Require all granted
+              DirectoryIndex index.php
+              AllowOverride All
+            </Directory>
+          '' + optionalString (cfg.uploadsDir != null) ''
+            Alias "/images" "${cfg.uploadsDir}"
+            <Directory "${cfg.uploadsDir}">
+              Require all granted
+            </Directory>
+          '';
+        }
+      ];
     };
 
     systemd.tmpfiles.rules = [
@@ -489,13 +549,14 @@ in
       };
     };
 
-    systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service"
-      ++ optional (cfg.database.createLocally && cfg.database.type == "postgres") "postgresql.service";
+    systemd.services.httpd.after = optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service"
+      ++ optional (cfg.webserver == "apache" && cfg.database.createLocally && cfg.database.type == "postgres") "postgresql.service";
 
     users.users.${user} = {
       group = group;
       isSystemUser = true;
     };
+    users.groups.${group} = {};
 
     environment.systemPackages = [ mediawikiScripts ];
   };
diff --git a/nixos/modules/services/web-apps/monica.nix b/nixos/modules/services/web-apps/monica.nix
new file mode 100644
index 0000000000000..442044fedb14e
--- /dev/null
+++ b/nixos/modules/services/web-apps/monica.nix
@@ -0,0 +1,468 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+with lib; let
+  cfg = config.services.monica;
+  monica = pkgs.monica.override {
+    dataDir = cfg.dataDir;
+  };
+  db = cfg.database;
+  mail = cfg.mail;
+
+  user = cfg.user;
+  group = cfg.group;
+
+  # shell script for local administration
+  artisan = pkgs.writeScriptBin "monica" ''
+    #! ${pkgs.runtimeShell}
+    cd ${monica}
+    sudo() {
+      if [[ "$USER" != ${user} ]]; then
+        exec /run/wrappers/bin/sudo -u ${user} "$@"
+      else
+        exec "$@"
+      fi
+    }
+    sudo ${pkgs.php}/bin/php artisan "$@"
+  '';
+
+  tlsEnabled = cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME;
+in {
+  options.services.monica = {
+    enable = mkEnableOption (lib.mdDoc "monica");
+
+    user = mkOption {
+      default = "monica";
+      description = lib.mdDoc "User monica runs as.";
+      type = types.str;
+    };
+
+    group = mkOption {
+      default = "monica";
+      description = lib.mdDoc "Group monica runs as.";
+      type = types.str;
+    };
+
+    appKeyFile = mkOption {
+      description = lib.mdDoc ''
+        A file containing the Laravel APP_KEY - a 32 character long,
+        base64 encoded key used for encryption where needed. Can be
+        generated with <code>head -c 32 /dev/urandom | base64</code>.
+      '';
+      example = "/run/keys/monica-appkey";
+      type = types.path;
+    };
+
+    hostname = lib.mkOption {
+      type = lib.types.str;
+      default =
+        if config.networking.domain != null
+        then config.networking.fqdn
+        else config.networking.hostName;
+      defaultText = lib.literalExpression "config.networking.fqdn";
+      example = "monica.example.com";
+      description = lib.mdDoc ''
+        The hostname to serve monica on.
+      '';
+    };
+
+    appURL = mkOption {
+      description = lib.mdDoc ''
+        The root URL that you want to host monica on. All URLs in monica will be generated using this value.
+        If you change this in the future you may need to run a command to update stored URLs in the database.
+        Command example: <code>php artisan monica:update-url https://old.example.com https://new.example.com</code>
+      '';
+      default = "http${lib.optionalString tlsEnabled "s"}://${cfg.hostname}";
+      defaultText = ''http''${lib.optionalString tlsEnabled "s"}://''${cfg.hostname}'';
+      example = "https://example.com";
+      type = types.str;
+    };
+
+    dataDir = mkOption {
+      description = lib.mdDoc "monica data directory";
+      default = "/var/lib/monica";
+      type = types.path;
+    };
+
+    database = {
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = lib.mdDoc "Database host address.";
+      };
+      port = mkOption {
+        type = types.port;
+        default = 3306;
+        description = lib.mdDoc "Database host port.";
+      };
+      name = mkOption {
+        type = types.str;
+        default = "monica";
+        description = lib.mdDoc "Database name.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = user;
+        defaultText = lib.literalExpression "user";
+        description = lib.mdDoc "Database username.";
+      };
+      passwordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        example = "/run/keys/monica-dbpassword";
+        description = lib.mdDoc ''
+          A file containing the password corresponding to
+          <option>database.user</option>.
+        '';
+      };
+      createLocally = mkOption {
+        type = types.bool;
+        default = true;
+        description = lib.mdDoc "Create the database and database user locally.";
+      };
+    };
+
+    mail = {
+      driver = mkOption {
+        type = types.enum ["smtp" "sendmail"];
+        default = "smtp";
+        description = lib.mdDoc "Mail driver to use.";
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = lib.mdDoc "Mail host address.";
+      };
+      port = mkOption {
+        type = types.port;
+        default = 1025;
+        description = lib.mdDoc "Mail host port.";
+      };
+      fromName = mkOption {
+        type = types.str;
+        default = "monica";
+        description = lib.mdDoc "Mail \"from\" name.";
+      };
+      from = mkOption {
+        type = types.str;
+        default = "mail@monica.com";
+        description = lib.mdDoc "Mail \"from\" email.";
+      };
+      user = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        example = "monica";
+        description = lib.mdDoc "Mail username.";
+      };
+      passwordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        example = "/run/keys/monica-mailpassword";
+        description = lib.mdDoc ''
+          A file containing the password corresponding to
+          <option>mail.user</option>.
+        '';
+      };
+      encryption = mkOption {
+        type = with types; nullOr (enum ["tls"]);
+        default = null;
+        description = lib.mdDoc "SMTP encryption mechanism to use.";
+      };
+    };
+
+    maxUploadSize = mkOption {
+      type = types.str;
+      default = "18M";
+      example = "1G";
+      description = lib.mdDoc "The maximum size for uploads (e.g. images).";
+    };
+
+    poolConfig = mkOption {
+      type = with types; attrsOf (oneOf [str int bool]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = lib.mdDoc ''
+        Options for the monica PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+        for details on configuration directives.
+      '';
+    };
+
+    nginx = mkOption {
+      type = types.submodule (
+        recursiveUpdate
+        (import ../web-servers/nginx/vhost-options.nix {inherit config lib;}) {}
+      );
+      default = {};
+      example = ''
+        {
+          serverAliases = [
+            "monica.''${config.networking.domain}"
+          ];
+          # To enable encryption and let let's encrypt take care of certificate
+          forceSSL = true;
+          enableACME = true;
+        }
+      '';
+      description = lib.mdDoc ''
+        With this option, you can customize the nginx virtualHost settings.
+      '';
+    };
+
+    config = mkOption {
+      type = with types;
+        attrsOf
+        (nullOr
+          (either
+            (oneOf [
+              bool
+              int
+              port
+              path
+              str
+            ])
+            (submodule {
+              options = {
+                _secret = mkOption {
+                  type = nullOr str;
+                  description = lib.mdDoc ''
+                    The path to a file containing the value the
+                    option should be set to in the final
+                    configuration file.
+                  '';
+                };
+              };
+            })));
+      default = {};
+      example = ''
+        {
+          ALLOWED_IFRAME_HOSTS = "https://example.com";
+          WKHTMLTOPDF = "/home/user/bins/wkhtmltopdf";
+          AUTH_METHOD = "oidc";
+          OIDC_NAME = "MyLogin";
+          OIDC_DISPLAY_NAME_CLAIMS = "name";
+          OIDC_CLIENT_ID = "monica";
+          OIDC_CLIENT_SECRET = {_secret = "/run/keys/oidc_secret"};
+          OIDC_ISSUER = "https://keycloak.example.com/auth/realms/My%20Realm";
+          OIDC_ISSUER_DISCOVER = true;
+        }
+      '';
+      description = lib.mdDoc ''
+        monica configuration options to set in the
+        <filename>.env</filename> file.
+
+        Refer to <link xlink:href="https://github.com/monicahq/monica"/>
+        for details on supported values.
+
+        Settings containing secret data should be set to an attribute
+        set containing the attribute <literal>_secret</literal> - a
+        string pointing to a file containing the value the option
+        should be set to. See the example to get a better picture of
+        this: in the resulting <filename>.env</filename> file, the
+        <literal>OIDC_CLIENT_SECRET</literal> key will be set to the
+        contents of the <filename>/run/keys/oidc_secret</filename>
+        file.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = db.createLocally -> db.user == user;
+        message = "services.monica.database.user must be set to ${user} if services.monica.database.createLocally is set true.";
+      }
+      {
+        assertion = db.createLocally -> db.passwordFile == null;
+        message = "services.monica.database.passwordFile cannot be specified if services.monica.database.createLocally is set to true.";
+      }
+    ];
+
+    services.monica.config = {
+      APP_ENV = "production";
+      APP_KEY._secret = cfg.appKeyFile;
+      APP_URL = cfg.appURL;
+      DB_HOST = db.host;
+      DB_PORT = db.port;
+      DB_DATABASE = db.name;
+      DB_USERNAME = db.user;
+      MAIL_DRIVER = mail.driver;
+      MAIL_FROM_NAME = mail.fromName;
+      MAIL_FROM = mail.from;
+      MAIL_HOST = mail.host;
+      MAIL_PORT = mail.port;
+      MAIL_USERNAME = mail.user;
+      MAIL_ENCRYPTION = mail.encryption;
+      DB_PASSWORD._secret = db.passwordFile;
+      MAIL_PASSWORD._secret = mail.passwordFile;
+      APP_SERVICES_CACHE = "/run/monica/cache/services.php";
+      APP_PACKAGES_CACHE = "/run/monica/cache/packages.php";
+      APP_CONFIG_CACHE = "/run/monica/cache/config.php";
+      APP_ROUTES_CACHE = "/run/monica/cache/routes-v7.php";
+      APP_EVENTS_CACHE = "/run/monica/cache/events.php";
+      SESSION_SECURE_COOKIE = tlsEnabled;
+    };
+
+    environment.systemPackages = [artisan];
+
+    services.mysql = mkIf db.createLocally {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [db.name];
+      ensureUsers = [
+        {
+          name = db.user;
+          ensurePermissions = {"${db.name}.*" = "ALL PRIVILEGES";};
+        }
+      ];
+    };
+
+    services.phpfpm.pools.monica = {
+      inherit user group;
+      phpOptions = ''
+        log_errors = on
+        post_max_size = ${cfg.maxUploadSize}
+        upload_max_filesize = ${cfg.maxUploadSize}
+      '';
+      settings = {
+        "listen.mode" = "0660";
+        "listen.owner" = user;
+        "listen.group" = group;
+      } // cfg.poolConfig;
+    };
+
+    services.nginx = {
+      enable = mkDefault true;
+      recommendedTlsSettings = true;
+      recommendedOptimisation = true;
+      recommendedGzipSettings = true;
+      recommendedBrotliSettings = true;
+      recommendedProxySettings = true;
+      virtualHosts.${cfg.hostname} = mkMerge [
+        cfg.nginx
+        {
+          root = mkForce "${monica}/public";
+          locations = {
+            "/" = {
+              index = "index.php";
+              tryFiles = "$uri $uri/ /index.php?$query_string";
+            };
+            "~ \.php$".extraConfig = ''
+              fastcgi_pass unix:${config.services.phpfpm.pools."monica".socket};
+            '';
+            "~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
+              extraConfig = "expires 365d;";
+            };
+          };
+        }
+      ];
+    };
+
+    systemd.services.monica-setup = {
+      description = "Preperation tasks for monica";
+      before = ["phpfpm-monica.service"];
+      after = optional db.createLocally "mysql.service";
+      wantedBy = ["multi-user.target"];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        User = user;
+        UMask = 077;
+        WorkingDirectory = "${monica}";
+        RuntimeDirectory = "monica/cache";
+        RuntimeDirectoryMode = 0700;
+      };
+      path = [pkgs.replace-secret];
+      script = let
+        isSecret = v: isAttrs v && v ? _secret && isString v._secret;
+        monicaEnvVars = lib.generators.toKeyValue {
+          mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
+            mkValueString = v:
+              with builtins;
+                if isInt v
+                then toString v
+                else if isString v
+                then v
+                else if true == v
+                then "true"
+                else if false == v
+                then "false"
+                else if isSecret v
+                then hashString "sha256" v._secret
+                else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
+          };
+        };
+        secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
+        mkSecretReplacement = file: ''
+          replace-secret ${escapeShellArgs [(builtins.hashString "sha256" file) file "${cfg.dataDir}/.env"]}
+        '';
+        secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
+        filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{} null])) cfg.config;
+        monicaEnv = pkgs.writeText "monica.env" (monicaEnvVars filteredConfig);
+      in ''
+        # error handling
+        set -euo pipefail
+
+        # create .env file
+        install -T -m 0600 -o ${user} ${monicaEnv} "${cfg.dataDir}/.env"
+        ${secretReplacements}
+        if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
+          sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
+        fi
+
+        # migrate & seed db
+        ${pkgs.php}/bin/php artisan key:generate --force
+        ${pkgs.php}/bin/php artisan setup:production -v --force
+      '';
+    };
+
+    systemd.services.monica-scheduler = {
+      description = "Background tasks for monica";
+      startAt = "minutely";
+      after = ["monica-setup.service"];
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        WorkingDirectory = "${monica}";
+        ExecStart = "${pkgs.php}/bin/php ${monica}/artisan schedule:run -v";
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${cfg.dataDir}                            0710 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public                     0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/public/uploads             0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage                    0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/app                0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/fonts              0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework          0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/cache    0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/framework/views    0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/logs               0700 ${user} ${group} - -"
+      "d ${cfg.dataDir}/storage/uploads            0700 ${user} ${group} - -"
+    ];
+
+    users = {
+      users = mkIf (user == "monica") {
+        monica = {
+          inherit group;
+          isSystemUser = true;
+        };
+        "${config.services.nginx.user}".extraGroups = [group];
+      };
+      groups = mkIf (group == "monica") {
+        monica = {};
+      };
+    };
+  };
+}
+
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
index 5f8d9c5b15f4c..b617e9a593795 100644
--- a/nixos/modules/services/web-apps/moodle.nix
+++ b/nixos/modules/services/web-apps/moodle.nix
@@ -56,7 +56,7 @@ let
   mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
   pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
 
-  phpExt = pkgs.php80.buildEnv {
+  phpExt = pkgs.php81.buildEnv {
     extensions = { all, ... }: with all; [ iconv mbstring curl openssl tokenizer soap ctype zip gd simplexml dom intl sqlite3 pgsql pdo_sqlite pdo_pgsql pdo_odbc pdo_mysql pdo mysqli session zlib xmlreader fileinfo filter opcache exif sodium ];
     extraConfig = "max_input_vars = 5000";
   };
diff --git a/nixos/modules/services/web-apps/nextcloud-notify_push.nix b/nixos/modules/services/web-apps/nextcloud-notify_push.nix
index e36631b6093c5..d6aeee081fc96 100644
--- a/nixos/modules/services/web-apps/nextcloud-notify_push.nix
+++ b/nixos/modules/services/web-apps/nextcloud-notify_push.nix
@@ -2,6 +2,7 @@
 
 let
   cfg = config.services.nextcloud.notify_push;
+  cfgN = config.services.nextcloud;
 in
 {
   options.services.nextcloud.notify_push = {
@@ -25,6 +26,16 @@ in
       default = "error";
       description = lib.mdDoc "Log level";
     };
+
+    bendDomainToLocalhost = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc ''
+        Wether to add an entry to `/etc/hosts` for the configured nextcloud domain to point to `localhost` and add `localhost `to nextcloud's `trusted_proxies` config option.
+
+        This is useful when nextcloud's domain is not a static IP address and when the reverse proxy cannot be bypassed because the backend connection is done via unix socket.
+      '';
+    };
   } // (
     lib.genAttrs [
       "dbtype"
@@ -44,11 +55,14 @@ in
 
   config = lib.mkIf cfg.enable {
     systemd.services.nextcloud-notify_push = let
-      nextcloudUrl = "http${lib.optionalString config.services.nextcloud.https "s"}://${config.services.nextcloud.hostName}";
+      nextcloudUrl = "http${lib.optionalString cfgN.https "s"}://${cfgN.hostName}";
     in {
       description = "Push daemon for Nextcloud clients";
       documentation = [ "https://github.com/nextcloud/notify_push" ];
-      after = [ "phpfpm-nextcloud.service" ];
+      after = [
+        "phpfpm-nextcloud.service"
+        "redis-nextcloud.service"
+      ];
       wantedBy = [ "multi-user.target" ];
       environment = {
         NEXTCLOUD_URL = nextcloudUrl;
@@ -57,7 +71,7 @@ in
         LOG = cfg.logLevel;
       };
       postStart = ''
-        ${config.services.nextcloud.occ}/bin/nextcloud-occ notify_push:setup ${nextcloudUrl}/push
+        ${cfgN.occ}/bin/nextcloud-occ notify_push:setup ${nextcloudUrl}/push
       '';
       script = let
         dbType = if cfg.dbtype == "pgsql" then "postgresql" else cfg.dbtype;
@@ -76,7 +90,7 @@ in
         export DATABASE_PASSWORD="$(<"${cfg.dbpassFile}")"
       '' + ''
         export DATABASE_URL="${dbUrl}"
-        ${cfg.package}/bin/notify_push --glob-config '${config.services.nextcloud.datadir}/config/config.php'
+        ${cfg.package}/bin/notify_push '${cfgN.datadir}/config/config.php'
       '';
       serviceConfig = {
         User = "nextcloud";
@@ -87,10 +101,23 @@ in
       };
     };
 
-    services.nginx.virtualHosts.${config.services.nextcloud.hostName}.locations."^~ /push/" = {
-      proxyPass = "http://unix:${cfg.socketPath}";
-      proxyWebsockets = true;
-      recommendedProxySettings = true;
+    networking.hosts = lib.mkIf cfg.bendDomainToLocalhost {
+      "127.0.0.1" = [ cfgN.hostName ];
+      "::1" = [ cfgN.hostName ];
     };
+
+    services = lib.mkMerge [
+      {
+        nginx.virtualHosts.${cfgN.hostName}.locations."^~ /push/" = {
+          proxyPass = "http://unix:${cfg.socketPath}";
+          proxyWebsockets = true;
+          recommendedProxySettings = true;
+        };
+      }
+
+      (lib.mkIf cfg.bendDomainToLocalhost {
+        nextcloud.extraOptions.trusted_proxies = [ "127.0.0.1" "::1" ];
+      })
+    ];
   };
 }
diff --git a/nixos/modules/services/web-apps/nextcloud.md b/nixos/modules/services/web-apps/nextcloud.md
index 7ef3cca281f9e..5be81a18dfecd 100644
--- a/nixos/modules/services/web-apps/nextcloud.md
+++ b/nixos/modules/services/web-apps/nextcloud.md
@@ -12,10 +12,17 @@ major version available.
 
 Nextcloud is a PHP-based application which requires an HTTP server
 ([`services.nextcloud`](#opt-services.nextcloud.enable)
-optionally supports
-[`services.nginx`](#opt-services.nginx.enable))
-and a database (it's recommended to use
-[`services.postgresql`](#opt-services.postgresql.enable)).
+and optionally supports
+[`services.nginx`](#opt-services.nginx.enable)).
+
+For the database, you can set
+[`services.nextcloud.config.dbtype`](#opt-services.nextcloud.config.dbtype) to
+either `sqlite` (the default), `mysql`, or `pgsql`. The simplest is `sqlite`,
+which will be automatically created and managed by the application. For the
+last two, you can easily create a local database by setting
+[`services.nextcloud.database.createLocally`](#opt-services.nextcloud.database.createLocally)
+to `true`, Nextcloud will automatically be configured to connect to it through
+socket.
 
 A very basic configuration may look like this:
 ```
@@ -24,32 +31,13 @@ A very basic configuration may look like this:
   services.nextcloud = {
     enable = true;
     hostName = "nextcloud.tld";
+    database.createLocally = true;
     config = {
       dbtype = "pgsql";
-      dbuser = "nextcloud";
-      dbhost = "/run/postgresql"; # nextcloud will add /.s.PGSQL.5432 by itself
-      dbname = "nextcloud";
       adminpassFile = "/path/to/admin-pass-file";
-      adminuser = "root";
     };
   };
 
-  services.postgresql = {
-    enable = true;
-    ensureDatabases = [ "nextcloud" ];
-    ensureUsers = [
-     { name = "nextcloud";
-       ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
-     }
-    ];
-  };
-
-  # ensure that postgres is running *before* running the setup
-  systemd.services."nextcloud-setup" = {
-    requires = ["postgresql.service"];
-    after = ["postgresql.service"];
-  };
-
   networking.firewall.allowedTCPPorts = [ 80 443 ];
 }
 ```
@@ -132,7 +120,9 @@ Auto updates for Nextcloud apps can be enabled using
     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 because this is implemented using the legacy cipher RC4.
+    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).
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index 76a0172747ffd..01dca43776892 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -57,6 +57,9 @@ let
 
   inherit (config.system) stateVersion;
 
+  mysqlLocal = cfg.database.createLocally && cfg.config.dbtype == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.config.dbtype == "pgsql";
+
 in {
 
   imports = [
@@ -204,7 +207,7 @@ in {
     package = mkOption {
       type = types.package;
       description = lib.mdDoc "Which package to use for the Nextcloud instance.";
-      relatedPackages = [ "nextcloud24" "nextcloud25" "nextcloud26" ];
+      relatedPackages = [ "nextcloud25" "nextcloud26" ];
     };
     phpPackage = mkOption {
       type = types.package;
@@ -316,11 +319,7 @@ in {
         type = types.bool;
         default = false;
         description = lib.mdDoc ''
-          Create the database and database user locally. Only available for
-          mysql database.
-          Note that this option will use the latest version of MariaDB which
-          is not officially supported by Nextcloud. As for now a workaround
-          is used to also support MariaDB version >= 10.6.
+          Create the database and database user locally.
         '';
       };
 
@@ -352,12 +351,15 @@ in {
       };
       dbhost = mkOption {
         type = types.nullOr types.str;
-        default = "localhost";
+        default =
+          if pgsqlLocal then "/run/postgresql"
+          else if mysqlLocal then "localhost:/run/mysqld/mysqld.sock"
+          else "localhost";
+        defaultText = "localhost";
         description = lib.mdDoc ''
-          Database host.
-
-          Note: for using Unix authentication with PostgreSQL, this should be
-          set to `/run/postgresql`.
+          Database host or socket path. Defaults to the correct unix socket
+          instead if `services.nextcloud.database.createLocally` is true and
+          `services.nextcloud.config.dbtype` is either `pgsql` or `mysql`.
         '';
       };
       dbport = mkOption {
@@ -549,6 +551,19 @@ in {
       default = true;
     };
 
+    configureRedis = lib.mkOption {
+      type = lib.types.bool;
+      default = config.services.nextcloud.notify_push.enable;
+      defaultText = literalExpression "config.services.nextcloud.notify_push.enable";
+      description = lib.mdDoc ''
+        Wether to configure nextcloud to use the recommended redis settings for small instances.
+
+        ::: {.note}
+        The `notify_push` app requires redis to be configured. If this option is turned off, this must be configured manually.
+        :::
+      '';
+    };
+
     caching = {
       apcu = mkOption {
         type = types.bool;
@@ -712,6 +727,10 @@ in {
           See <https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/encryption_configuration.html#disabling-encryption> on how to achieve this.
 
           For more context, here is the implementing pull request: https://github.com/NixOS/nixpkgs/pull/198470
+        '')
+        ++ (optional (cfg.enableBrokenCiphersForSSE && versionAtLeast cfg.package.version "26") ''
+          Nextcloud26 supports RC4 without requiring legacy OpenSSL, so
+          `services.nextcloud.enableBrokenCiphersForSSE` can be set to `false`.
         '');
 
       services.nextcloud.package = with pkgs;
@@ -733,8 +752,21 @@ in {
     }
 
     { assertions = [
-      { assertion = cfg.database.createLocally -> cfg.config.dbtype == "mysql";
-        message = ''services.nextcloud.config.dbtype must be set to mysql if services.nextcloud.database.createLocally is set to true.'';
+      { assertion = cfg.database.createLocally -> cfg.config.dbpassFile == null;
+        message = ''
+          Using `services.nextcloud.database.createLocally` with database
+          password authentication is no longer supported.
+
+          If you use an external database (or want to use password auth for any
+          other reason), set `services.nextcloud.database.createLocally` to
+          `false`. The database won't be managed for you (use `services.mysql`
+          if you want to set it up).
+
+          If you want this module to manage your nextcloud database for you,
+          unset `services.nextcloud.config.dbpassFile` and
+          `services.nextcloud.config.dbhost` to use socket authentication
+          instead of password.
+        '';
       }
     ]; }
 
@@ -898,6 +930,8 @@ in {
         in {
           wantedBy = [ "multi-user.target" ];
           before = [ "phpfpm-nextcloud.service" ];
+          after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+          requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
           path = [ occ ];
           script = ''
             ${optionalString (c.dbpassFile != null) ''
@@ -1003,7 +1037,7 @@ in {
 
       environment.systemPackages = [ occ ];
 
-      services.mysql = lib.mkIf cfg.database.createLocally {
+      services.mysql = lib.mkIf mysqlLocal {
         enable = true;
         package = lib.mkDefault pkgs.mariadb;
         ensureDatabases = [ cfg.config.dbname ];
@@ -1011,14 +1045,34 @@ in {
           name = cfg.config.dbuser;
           ensurePermissions = { "${cfg.config.dbname}.*" = "ALL PRIVILEGES"; };
         }];
-        initialScript = pkgs.writeText "mysql-init" ''
-          CREATE USER '${cfg.config.dbname}'@'localhost' IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}';
-          CREATE DATABASE IF NOT EXISTS ${cfg.config.dbname};
-          GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
-            CREATE TEMPORARY TABLES ON ${cfg.config.dbname}.* TO '${cfg.config.dbuser}'@'localhost'
-            IDENTIFIED BY '${builtins.readFile( cfg.config.dbpassFile )}';
-          FLUSH privileges;
-        '';
+      };
+
+      services.postgresql = mkIf pgsqlLocal {
+        enable = true;
+        ensureDatabases = [ cfg.config.dbname ];
+        ensureUsers = [{
+          name = cfg.config.dbuser;
+          ensurePermissions = { "DATABASE ${cfg.config.dbname}" = "ALL PRIVILEGES"; };
+        }];
+      };
+
+      services.redis.servers.nextcloud = lib.mkIf cfg.configureRedis {
+        enable = true;
+        user = "nextcloud";
+      };
+
+      services.nextcloud = lib.mkIf cfg.configureRedis {
+        caching.redis = true;
+        extraOptions = {
+          memcache = {
+            distributed = ''\OC\Memcache\Redis'';
+            locking = ''\OC\Memcache\Redis'';
+          };
+          redis = {
+            host = config.services.redis.servers.nextcloud.unixSocket;
+            port = 0;
+          };
+        };
       };
 
       services.nginx.enable = mkDefault true;
diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix
index 7e418f2869c85..4ef2d7dce532c 100644
--- a/nixos/modules/services/web-apps/peertube.nix
+++ b/nixos/modules/services/web-apps/peertube.nix
@@ -429,7 +429,7 @@ in {
 
       environment = env;
 
-      path = with pkgs; [ bashInteractive ffmpeg nodejs-16_x openssl yarn python3 ];
+      path = with pkgs; [ bashInteractive ffmpeg nodejs_18 openssl yarn python3 ];
 
       script = ''
         #!/bin/sh
@@ -490,7 +490,7 @@ in {
     services.nginx = lib.mkIf cfg.configureNginx {
       enable = true;
       virtualHosts."${cfg.localDomain}" = {
-        root = "/var/lib/peertube";
+        root = "/var/lib/peertube/www";
 
         # Application
         locations."/" = {
@@ -593,7 +593,7 @@ in {
 
         # Bypass PeerTube for performance reasons.
         locations."~ ^/client/(assets/images/(icons/icon-36x36\.png|icons/icon-48x48\.png|icons/icon-72x72\.png|icons/icon-96x96\.png|icons/icon-144x144\.png|icons/icon-192x192\.png|icons/icon-512x512\.png|logo\.svg|favicon\.png|default-playlist\.jpg|default-avatar-account\.png|default-avatar-account-48x48\.png|default-avatar-video-channel\.png|default-avatar-video-channel-48x48\.png))$" = {
-          tryFiles = "/www/client-overrides/$1 /www/client/$1 $1";
+          tryFiles = "/client-overrides/$1 /client/$1 $1";
           priority = 1310;
         };
 
@@ -859,7 +859,7 @@ in {
           home = cfg.package;
         };
       })
-      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs-16_x pkgs.yarn ])
+      (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs_18 pkgs.yarn ])
       (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
     ];
 
diff --git a/nixos/modules/services/web-apps/pict-rs.nix b/nixos/modules/services/web-apps/pict-rs.nix
index 0f13b2ae6db13..3270715a051ba 100644
--- a/nixos/modules/services/web-apps/pict-rs.nix
+++ b/nixos/modules/services/web-apps/pict-rs.nix
@@ -34,8 +34,8 @@ in
   config = lib.mkIf cfg.enable {
     systemd.services.pict-rs = {
       environment = {
-        PICTRS_PATH = cfg.dataDir;
-        PICTRS_ADDR = "${cfg.address}:${toString cfg.port}";
+        PICTRS__PATH = cfg.dataDir;
+        PICTRS__ADDR = "${cfg.address}:${toString cfg.port}";
       };
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
diff --git a/nixos/modules/services/web-apps/pixelfed.nix b/nixos/modules/services/web-apps/pixelfed.nix
new file mode 100644
index 0000000000000..817d0f9b60f50
--- /dev/null
+++ b/nixos/modules/services/web-apps/pixelfed.nix
@@ -0,0 +1,478 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pixelfed;
+  user = cfg.user;
+  group = cfg.group;
+  pixelfed = cfg.package.override { inherit (cfg) dataDir runtimeDir; };
+  # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L185-L190
+  extraPrograms = with pkgs; [ jpegoptim optipng pngquant gifsicle ffmpeg ];
+  # Ensure PHP extensions: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L135-L147
+  phpPackage = cfg.phpPackage.buildEnv {
+    extensions = { enabled, all }:
+      enabled
+      ++ (with all; [ bcmath ctype curl mbstring gd intl zip redis imagick ]);
+  };
+  configFile =
+    pkgs.writeText "pixelfed-env" (lib.generators.toKeyValue { } cfg.settings);
+  # Management script
+  pixelfed-manage = pkgs.writeShellScriptBin "pixelfed-manage" ''
+    cd ${pixelfed}
+    sudo=exec
+    if [[ "$USER" != ${user} ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u ${user}'
+    fi
+    $sudo ${cfg.phpPackage}/bin/php artisan "$@"
+  '';
+  dbSocket = {
+    "pgsql" = "/run/postgresql";
+    "mysql" = "/run/mysqld/mysqld.sock";
+  }.${cfg.database.type};
+  dbService = {
+    "pgsql" = "postgresql.service";
+    "mysql" = "mysql.service";
+  }.${cfg.database.type};
+  redisService = "redis-pixelfed.service";
+in {
+  options.services = {
+    pixelfed = {
+      enable = mkEnableOption (lib.mdDoc "a Pixelfed instance");
+      package = mkPackageOptionMD pkgs "pixelfed" { };
+      phpPackage = mkPackageOptionMD pkgs "php81" { };
+
+      user = mkOption {
+        type = types.str;
+        default = "pixelfed";
+        description = lib.mdDoc ''
+          User account under which pixelfed runs.
+
+          ::: {.note}
+          If left as the default value this user will automatically be created
+          on system activation, otherwise you are responsible for
+          ensuring the user exists before the pixelfed application starts.
+          :::
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "pixelfed";
+        description = lib.mdDoc ''
+          Group account under which pixelfed runs.
+
+          ::: {.note}
+          If left as the default value this group will automatically be created
+          on system activation, otherwise you are responsible for
+          ensuring the group exists before the pixelfed application starts.
+          :::
+        '';
+      };
+
+      domain = mkOption {
+        type = types.str;
+        description = lib.mdDoc ''
+          FQDN for the Pixelfed instance.
+        '';
+      };
+
+      secretFile = mkOption {
+        type = types.path;
+        description = lib.mdDoc ''
+          A secret file to be sourced for the .env settings.
+          Place `APP_KEY` and other settings that should not end up in the Nix store here.
+        '';
+      };
+
+      settings = mkOption {
+        type = with types; (attrsOf (oneOf [ bool int str ]));
+        description = lib.mdDoc ''
+          .env settings for Pixelfed.
+          Secrets should use `secretFile` option instead.
+        '';
+      };
+
+      nginx = mkOption {
+        type = types.nullOr (types.submodule
+          (import ../web-servers/nginx/vhost-options.nix {
+            inherit config lib;
+          }));
+        default = null;
+        example = lib.literalExpression ''
+          {
+            serverAliases = [
+              "pics.''${config.networking.domain}"
+            ];
+            enableACME = true;
+            forceHttps = true;
+          }
+        '';
+        description = lib.mdDoc ''
+          With this option, you can customize an nginx virtual host which already has sensible defaults for Dolibarr.
+          Set to {} if you do not need any customization to the virtual host.
+          If enabled, then by default, the {option}`serverName` is
+          `''${domain}`,
+          If this is set to null (the default), no nginx virtualHost will be configured.
+        '';
+      };
+
+      redis.createLocally = mkEnableOption
+        (lib.mdDoc "a local Redis database using UNIX socket authentication")
+        // {
+          default = true;
+        };
+
+      database = {
+        createLocally = mkEnableOption
+          (lib.mdDoc "a local database using UNIX socket authentication") // {
+            default = true;
+          };
+        automaticMigrations = mkEnableOption
+          (lib.mdDoc "automatic migrations for database schema and data") // {
+            default = true;
+          };
+
+        type = mkOption {
+          type = types.enum [ "mysql" "pgsql" ];
+          example = "pgsql";
+          default = "mysql";
+          description = lib.mdDoc ''
+            Database engine to use.
+            Note that PGSQL is not well supported: https://github.com/pixelfed/pixelfed/issues/2727
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "pixelfed";
+          description = lib.mdDoc "Database name.";
+        };
+      };
+
+      maxUploadSize = mkOption {
+        type = types.str;
+        default = "8M";
+        description = lib.mdDoc ''
+          Max upload size with units.
+        '';
+      };
+
+      poolConfig = mkOption {
+        type = with types; attrsOf (oneOf [ int str bool ]);
+        default = { };
+
+        description = lib.mdDoc ''
+          Options for Pixelfed's PHP-FPM pool.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/pixelfed";
+        description = lib.mdDoc ''
+          State directory of the `pixelfed` user which holds
+          the application's state and data.
+        '';
+      };
+
+      runtimeDir = mkOption {
+        type = types.str;
+        default = "/run/pixelfed";
+        description = lib.mdDoc ''
+          Ruutime directory of the `pixelfed` user which holds
+          the application's caches and temporary files.
+        '';
+      };
+
+      schedulerInterval = mkOption {
+        type = types.str;
+        default = "1d";
+        description = lib.mdDoc "How often the Pixelfed cron task should run";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.pixelfed = mkIf (cfg.user == "pixelfed") {
+      isSystemUser = true;
+      group = cfg.group;
+      extraGroups = lib.optional cfg.redis.createLocally "redis-pixelfed";
+    };
+    users.groups.pixelfed = mkIf (cfg.group == "pixelfed") { };
+
+    services.redis.servers.pixelfed.enable = lib.mkIf cfg.redis.createLocally true;
+    services.pixelfed.settings = mkMerge [
+      ({
+        APP_ENV = mkDefault "production";
+        APP_DEBUG = mkDefault false;
+        # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L312-L316
+        APP_URL = mkDefault "https://${cfg.domain}";
+        ADMIN_DOMAIN = mkDefault cfg.domain;
+        APP_DOMAIN = mkDefault cfg.domain;
+        SESSION_DOMAIN = mkDefault cfg.domain;
+        SESSION_SECURE_COOKIE = mkDefault true;
+        OPEN_REGISTRATION = mkDefault false;
+        # ActivityPub: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L360-L364
+        ACTIVITY_PUB = mkDefault true;
+        AP_REMOTE_FOLLOW = mkDefault true;
+        AP_INBOX = mkDefault true;
+        AP_OUTBOX = mkDefault true;
+        AP_SHAREDINBOX = mkDefault true;
+        # Image optimization: https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L367-L404
+        PF_OPTIMIZE_IMAGES = mkDefault true;
+        IMAGE_DRIVER = mkDefault "imagick";
+        # Mobile APIs
+        OAUTH_ENABLED = mkDefault true;
+        # https://github.com/pixelfed/pixelfed/blob/dev/app/Console/Commands/Installer.php#L351
+        EXP_EMC = mkDefault true;
+        # Defer to systemd
+        LOG_CHANNEL = mkDefault "stderr";
+        # TODO: find out the correct syntax?
+        # TRUST_PROXIES = mkDefault "127.0.0.1/8, ::1/128";
+      })
+      (mkIf (cfg.redis.createLocally) {
+        BROADCAST_DRIVER = mkDefault "redis";
+        CACHE_DRIVER = mkDefault "redis";
+        QUEUE_DRIVER = mkDefault "redis";
+        SESSION_DRIVER = mkDefault "redis";
+        WEBSOCKET_REPLICATION_MODE = mkDefault "redis";
+        # Suppport phpredis and predis configuration-style.
+        REDIS_SCHEME = "unix";
+        REDIS_HOST = config.services.redis.servers.pixelfed.unixSocket;
+        REDIS_PATH = config.services.redis.servers.pixelfed.unixSocket;
+      })
+      (mkIf (cfg.database.createLocally) {
+        DB_CONNECTION = cfg.database.type;
+        DB_SOCKET = dbSocket;
+        DB_DATABASE = cfg.database.name;
+        DB_USERNAME = user;
+        # No TCP/IP connection.
+        DB_PORT = 0;
+      })
+    ];
+
+    environment.systemPackages = [ pixelfed-manage ];
+
+    services.mysql =
+      mkIf (cfg.database.createLocally && cfg.database.type == "mysql") {
+        enable = mkDefault true;
+        package = mkDefault pkgs.mariadb;
+        ensureDatabases = [ cfg.database.name ];
+        ensureUsers = [{
+          name = user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }];
+      };
+
+    services.postgresql =
+      mkIf (cfg.database.createLocally && cfg.database.type == "pgsql") {
+        enable = mkDefault true;
+        ensureDatabases = [ cfg.database.name ];
+        ensureUsers = [{
+          name = user;
+          ensurePermissions = { };
+        }];
+      };
+
+    # Make each individual option overridable with lib.mkDefault.
+    services.pixelfed.poolConfig = lib.mapAttrs' (n: v: lib.nameValuePair n (lib.mkDefault v)) {
+      "pm" = "dynamic";
+      "php_admin_value[error_log]" = "stderr";
+      "php_admin_flag[log_errors]" = true;
+      "catch_workers_output" = true;
+      "pm.max_children" = "32";
+      "pm.start_servers" = "2";
+      "pm.min_spare_servers" = "2";
+      "pm.max_spare_servers" = "4";
+      "pm.max_requests" = "500";
+    };
+
+    services.phpfpm.pools.pixelfed = {
+      inherit user group;
+      inherit phpPackage;
+
+      phpOptions = ''
+        post_max_size = ${toString cfg.maxUploadSize}
+        upload_max_filesize = ${toString cfg.maxUploadSize}
+        max_execution_time = 600;
+      '';
+
+      settings = {
+        "listen.owner" = user;
+        "listen.group" = group;
+        "listen.mode" = "0660";
+        "catch_workers_output" = "yes";
+      } // cfg.poolConfig;
+    };
+
+    systemd.services.phpfpm-pixelfed.after = [ "pixelfed-data-setup.service" ];
+    systemd.services.phpfpm-pixelfed.requires =
+      [ "pixelfed-horizon.service" "pixelfed-data-setup.service" ]
+      ++ lib.optional cfg.database.createLocally dbService
+      ++ lib.optional cfg.redis.createLocally redisService;
+    # Ensure image optimizations programs are available.
+    systemd.services.phpfpm-pixelfed.path = extraPrograms;
+
+    systemd.services.pixelfed-horizon = {
+      description = "Pixelfed task queueing via Laravel Horizon framework";
+      after = [ "network.target" "pixelfed-data-setup.service" ];
+      requires = [ "pixelfed-data-setup.service" ]
+        ++ (lib.optional cfg.database.createLocally dbService)
+        ++ (lib.optional cfg.redis.createLocally redisService);
+      wantedBy = [ "multi-user.target" ];
+      # Ensure image optimizations programs are available.
+      path = extraPrograms;
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pixelfed-manage}/bin/pixelfed-manage horizon";
+        StateDirectory =
+          lib.mkIf (cfg.dataDir == "/var/lib/pixelfed") "pixelfed";
+        User = user;
+        Group = group;
+        Restart = "on-failure";
+      };
+    };
+
+    systemd.timers.pixelfed-cron = {
+      description = "Pixelfed periodic tasks timer";
+      after = [ "pixelfed-data-setup.service" ];
+      requires = [ "phpfpm-pixelfed.service" ];
+      wantedBy = [ "timers.target" ];
+
+      timerConfig = {
+        OnBootSec = cfg.schedulerInterval;
+        OnUnitActiveSec = cfg.schedulerInterval;
+      };
+    };
+
+    systemd.services.pixelfed-cron = {
+      description = "Pixelfed periodic tasks";
+      # Ensure image optimizations programs are available.
+      path = extraPrograms;
+
+      serviceConfig = {
+        ExecStart = "${pixelfed-manage}/bin/pixelfed-manage schedule:run";
+        User = user;
+        Group = group;
+        StateDirectory = cfg.dataDir;
+      };
+    };
+
+    systemd.services.pixelfed-data-setup = {
+      description =
+        "Pixelfed setup: migrations, environment file update, cache reload, data changes";
+      wantedBy = [ "multi-user.target" ];
+      after = lib.optional cfg.database.createLocally dbService;
+      requires = lib.optional cfg.database.createLocally dbService;
+      path = with pkgs; [ bash pixelfed-manage rsync ] ++ extraPrograms;
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        Group = group;
+        StateDirectory =
+          lib.mkIf (cfg.dataDir == "/var/lib/pixelfed") "pixelfed";
+        LoadCredential = "env-secrets:${cfg.secretFile}";
+        UMask = "077";
+      };
+
+      script = ''
+        # Concatenate non-secret .env and secret .env
+        rm -f ${cfg.dataDir}/.env
+        cp --no-preserve=all ${configFile} ${cfg.dataDir}/.env
+        echo -e '\n' >> ${cfg.dataDir}/.env
+        cat "$CREDENTIALS_DIRECTORY/env-secrets" >> ${cfg.dataDir}/.env
+
+        # Link the static storage (package provided) to the runtime storage
+        # Necessary for cities.json and static images.
+        mkdir -p ${cfg.dataDir}/storage
+        rsync -av --no-perms ${pixelfed}/storage-static/ ${cfg.dataDir}/storage
+        chmod -R +w ${cfg.dataDir}/storage
+
+        # Link the app.php in the runtime folder.
+        # We cannot link the cache folder only because bootstrap folder needs to be writeable.
+        ln -sf ${pixelfed}/bootstrap-static/app.php ${cfg.runtimeDir}/app.php
+
+        # https://laravel.com/docs/10.x/filesystem#the-public-disk
+        # Creating the public/storage → storage/app/public link
+        # is unnecessary as it's part of the installPhase of pixelfed.
+
+        # Install Horizon
+        # FIXME: require write access to public/ — should be done as part of install — pixelfed-manage horizon:publish
+
+        # Before running any PHP program, cleanup the bootstrap.
+        # It's necessary if you upgrade the application otherwise you might
+        # try to import non-existent modules.
+        rm -rf ${cfg.runtimeDir}/bootstrap/*
+
+        # Perform the first migration.
+        [[ ! -f ${cfg.dataDir}/.initial-migration ]] && pixelfed-manage migrate --force && touch ${cfg.dataDir}/.initial-migration
+
+        ${lib.optionalString cfg.database.automaticMigrations ''
+          # Force migrate the database.
+          pixelfed-manage migrate --force
+        ''}
+
+        # Import location data
+        pixelfed-manage import:cities
+
+        ${lib.optionalString cfg.settings.ACTIVITY_PUB ''
+          # ActivityPub federation bookkeeping
+          [[ ! -f ${cfg.dataDir}/.instance-actor-created ]] && pixelfed-manage instance:actor && touch ${cfg.dataDir}/.instance-actor-created
+        ''}
+
+        ${lib.optionalString cfg.settings.OAUTH_ENABLED ''
+          # Generate Passport encryption keys
+          [[ ! -f ${cfg.dataDir}/.passport-keys-generated ]] && pixelfed-manage passport:keys && touch ${cfg.dataDir}/.passport-keys-generated
+        ''}
+
+        pixelfed-manage route:cache
+        pixelfed-manage view:cache
+        pixelfed-manage config:cache
+      '';
+    };
+
+    systemd.tmpfiles.rules = [
+      # Cache must live across multiple systemd units runtimes.
+      "d ${cfg.runtimeDir}/                         0700 ${user} ${group} - -"
+      "d ${cfg.runtimeDir}/cache                    0700 ${user} ${group} - -"
+    ];
+
+    # Enable NGINX to access our phpfpm-socket.
+    users.users."${config.services.nginx.group}".extraGroups = [ cfg.group ];
+    services.nginx = mkIf (cfg.nginx != null) {
+      enable = true;
+      virtualHosts."${cfg.domain}" = mkMerge [
+        cfg.nginx
+        {
+          root = lib.mkForce "${pixelfed}/public/";
+          locations."/".tryFiles = "$uri $uri/ /index.php?query_string";
+          locations."/favicon.ico".extraConfig = ''
+            access_log off; log_not_found off;
+          '';
+          locations."/robots.txt".extraConfig = ''
+            access_log off; log_not_found off;
+          '';
+          locations."~ \\.php$".extraConfig = ''
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+            fastcgi_pass unix:${config.services.phpfpm.pools.pixelfed.socket};
+            fastcgi_index index.php;
+          '';
+          locations."~ /\\.(?!well-known).*".extraConfig = ''
+            deny all;
+          '';
+          extraConfig = ''
+            add_header X-Frame-Options "SAMEORIGIN";
+            add_header X-XSS-Protection "1; mode=block";
+            add_header X-Content-Type-Options "nosniff";
+            index index.html index.htm index.php;
+            error_page 404 /index.php;
+            client_max_body_size ${toString cfg.maxUploadSize};
+          '';
+        }
+      ];
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/plausible.nix b/nixos/modules/services/web-apps/plausible.nix
index f64254d62524e..893dfa10acbc0 100644
--- a/nixos/modules/services/web-apps/plausible.nix
+++ b/nixos/modules/services/web-apps/plausible.nix
@@ -9,6 +9,8 @@ in {
   options.services.plausible = {
     enable = mkEnableOption (lib.mdDoc "plausible");
 
+    package = mkPackageOptionMD pkgs "plausible" { };
+
     releaseCookiePath = mkOption {
       type = with types; either str path;
       description = lib.mdDoc ''
@@ -180,12 +182,12 @@ in {
 
     services.epmd.enable = true;
 
-    environment.systemPackages = [ pkgs.plausible ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services = mkMerge [
       {
         plausible = {
-          inherit (pkgs.plausible.meta) description;
+          inherit (cfg.package.meta) description;
           documentation = [ "https://plausible.io/docs/self-hosting" ];
           wantedBy = [ "multi-user.target" ];
           after = optional cfg.database.clickhouse.setup "clickhouse.service"
@@ -233,7 +235,7 @@ in {
             SMTP_USER_NAME = cfg.mail.smtp.user;
           });
 
-          path = [ pkgs.plausible ]
+          path = [ cfg.package ]
             ++ optional cfg.database.postgres.setup config.services.postgresql.package;
           script = ''
             export CONFIG_DIR=$CREDENTIALS_DIRECTORY
@@ -241,10 +243,10 @@ in {
             export RELEASE_COOKIE="$(< $CREDENTIALS_DIRECTORY/RELEASE_COOKIE )"
 
             # setup
-            ${pkgs.plausible}/createdb.sh
-            ${pkgs.plausible}/migrate.sh
+            ${cfg.package}/createdb.sh
+            ${cfg.package}/migrate.sh
             ${optionalString cfg.adminUser.activate ''
-              if ! ${pkgs.plausible}/init-admin.sh | grep 'already exists'; then
+              if ! ${cfg.package}/init-admin.sh | grep 'already exists'; then
                 psql -d plausible <<< "UPDATE users SET email_verified=true;"
               fi
             ''}
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 6f494fae4cc18..3102e6a469536 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -534,7 +534,7 @@ let
     services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
       ${poolName} = {
         inherit (cfg) user;
-        phpPackage = pkgs.php80;
+        phpPackage = pkgs.php81;
         settings = mapAttrs (name: mkDefault) {
           "listen.owner" = "nginx";
           "listen.group" = "nginx";
diff --git a/nixos/modules/services/web-apps/vikunja.nix b/nixos/modules/services/web-apps/vikunja.nix
index c3552200d4e53..8bc8e8c29259e 100644
--- a/nixos/modules/services/web-apps/vikunja.nix
+++ b/nixos/modules/services/web-apps/vikunja.nix
@@ -56,6 +56,11 @@ in {
       type = types.str;
       description = lib.mdDoc "The Hostname under which the frontend is running.";
     };
+    port = mkOption {
+      type = types.port;
+      default = 3456;
+      description = lib.mdDoc "The TCP port exposed by the API.";
+    };
 
     settings = mkOption {
       type = format.type;
@@ -101,6 +106,7 @@ in {
         inherit (cfg.database) type host user database path;
       };
       service = {
+        interface = ":${toString cfg.port}";
         frontendurl = "${cfg.frontendScheme}://${cfg.frontendHostname}/";
       };
       files = {
@@ -132,7 +138,7 @@ in {
           tryFiles = "try_files $uri $uri/ /";
         };
         "~* ^/(api|dav|\\.well-known)/" = {
-          proxyPass = "http://localhost:3456";
+          proxyPass = "http://localhost:${toString cfg.port}";
           extraConfig = ''
             client_max_body_size 20M;
           '';
diff --git a/nixos/modules/services/web-apps/wiki-js.nix b/nixos/modules/services/web-apps/wiki-js.nix
index b0210c8a5d2be..631740f51ce35 100644
--- a/nixos/modules/services/web-apps/wiki-js.nix
+++ b/nixos/modules/services/web-apps/wiki-js.nix
@@ -133,7 +133,7 @@ in {
         WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
         DynamicUser = true;
         PrivateTmp = true;
-        ExecStart = "${pkgs.nodejs-16_x}/bin/node ${pkgs.wiki-js}/server";
+        ExecStart = "${pkgs.nodejs_18}/bin/node ${pkgs.wiki-js}/server";
       };
     };
   };
diff --git a/nixos/modules/services/web-servers/fcgiwrap.nix b/nixos/modules/services/web-servers/fcgiwrap.nix
index f9c91fb35db23..3a57ef383065b 100644
--- a/nixos/modules/services/web-servers/fcgiwrap.nix
+++ b/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -54,7 +54,7 @@ in {
 
       serviceConfig = {
         ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${builtins.toString cfg.preforkProcesses} ${
-          if (cfg.socketType != "unix") then "-s ${cfg.socketType}:${cfg.socketAddress}" else ""
+          optionalString (cfg.socketType != "unix") "-s ${cfg.socketType}:${cfg.socketAddress}"
         }";
       } // (if cfg.user != null && cfg.group != null then {
         User = cfg.user;
diff --git a/nixos/modules/services/web-servers/lighttpd/default.nix b/nixos/modules/services/web-servers/lighttpd/default.nix
index 811afe8e0af66..0438e12e7da82 100644
--- a/nixos/modules/services/web-servers/lighttpd/default.nix
+++ b/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -64,7 +64,7 @@ let
   ];
 
   maybeModuleString = moduleName:
-    if elem moduleName cfg.enableModules then ''"${moduleName}"'' else "";
+    optionalString (elem moduleName cfg.enableModules) ''"${moduleName}"'';
 
   modulesIncludeString = concatStringsSep ",\n"
     (filter (x: x != "") (map maybeModuleString allKnownModules));
@@ -106,15 +106,15 @@ let
       static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
       index-file.names = ( "index.html" )
 
-      ${if cfg.mod_userdir then ''
+      ${optionalString cfg.mod_userdir ''
         userdir.path = "public_html"
-      '' else ""}
+      ''}
 
-      ${if cfg.mod_status then ''
+      ${optionalString cfg.mod_status ''
         status.status-url = "/server-status"
         status.statistics-url = "/server-statistics"
         status.config-url = "/server-config"
-      '' else ""}
+      ''}
 
       ${cfg.extraConfig}
     '';
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 3d19186e1a9d3..f5fa2ef5f866a 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -113,10 +113,15 @@ let
     ]};
   '') (filterAttrs (name: conf: conf.enable) cfg.proxyCachePath));
 
+  toUpstreamParameter = key: value:
+    if builtins.isBool value
+    then lib.optionalString value key
+    else "${key}=${toString value}";
+
   upstreamConfig = toString (flip mapAttrsToList cfg.upstreams (name: upstream: ''
     upstream ${name} {
       ${toString (flip mapAttrsToList upstream.servers (name: server: ''
-        server ${name} ${optionalString server.backup "backup"};
+        server ${name} ${concatStringsSep " " (mapAttrsToList toUpstreamParameter server)};
       ''))}
       ${upstream.extraConfig}
     }
@@ -256,8 +261,6 @@ let
 
       ${proxyCachePathConfig}
 
-      ${vhosts}
-
       ${optionalString cfg.statusPage ''
         server {
           listen ${toString cfg.defaultHTTPListenPort};
@@ -275,6 +278,8 @@ let
         }
       ''}
 
+      ${vhosts}
+
       ${cfg.appendHttpConfig}
     }''}
 
@@ -318,7 +323,7 @@ let
 
         listenString = { addr, port, ssl, extraParameters ? [], ... }:
           # UDP listener for QUIC transport protocol.
-          (if ssl && vhost.quic then "
+          (optionalString (ssl && vhost.quic) ("
             listen ${addr}:${toString port} quic "
           + optionalString vhost.default "default_server "
           + optionalString vhost.reuseport "reuseport "
@@ -326,7 +331,7 @@ let
             let inCompatibleParameters = [ "ssl" "proxy_protocol" "http2" ];
                 isCompatibleParameter = param: !(any (p: p == param) inCompatibleParameters);
             in filter isCompatibleParameter extraParameters))
-          + ";" else "")
+          + ";"))
           + "
 
             listen ${addr}:${toString port} "
@@ -922,6 +927,7 @@ in
           options = {
             servers = mkOption {
               type = types.attrsOf (types.submodule {
+                freeformType = types.attrsOf (types.oneOf [ types.bool types.int types.str ]);
                 options = {
                   backup = mkOption {
                     type = types.bool;
@@ -935,9 +941,11 @@ in
               });
               description = lib.mdDoc ''
                 Defines the address and other parameters of the upstream servers.
+                See [the documentation](https://nginx.org/en/docs/http/ngx_http_upstream_module.html#server)
+                for the available parameters.
               '';
               default = {};
-              example = { "127.0.0.1:8000" = {}; };
+              example = lib.literalMD "see [](#opt-services.nginx.upstreams)";
             };
             extraConfig = mkOption {
               type = types.lines;
@@ -952,14 +960,23 @@ in
           Defines a group of servers to use as proxy target.
         '';
         default = {};
-        example = literalExpression ''
-          "backend_server" = {
-            servers = { "127.0.0.1:8000" = {}; };
-            extraConfig = ''''
+        example = {
+          "backend" = {
+            servers = {
+              "backend1.example.com:8080" = { weight = 5; };
+              "backend2.example.com" = { max_fails = 3; fail_timeout = "30s"; };
+              "backend3.example.com" = {};
+              "backup1.example.com" = { backup = true; };
+              "backup2.example.com" = { backup = true; };
+            };
+            extraConfig = ''
               keepalive 16;
-            '''';
+            '';
           };
-        '';
+          "memcached" = {
+            servers."unix:/run//memcached/memcached.sock" = {};
+          };
+        };
       };
 
       virtualHosts = mkOption {
diff --git a/nixos/modules/services/web-servers/stargazer.nix b/nixos/modules/services/web-servers/stargazer.nix
new file mode 100644
index 0000000000000..ddb9e7d8ba1f8
--- /dev/null
+++ b/nixos/modules/services/web-servers/stargazer.nix
@@ -0,0 +1,226 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.stargazer;
+  globalSection = ''
+    listen = ${lib.concatStringsSep " " cfg.listen}
+    connection-logging = ${lib.boolToString cfg.connectionLogging}
+    log-ip = ${lib.boolToString cfg.ipLog}
+    log-ip-partial = ${lib.boolToString cfg.ipLogPartial}
+    request-timeout = ${toString cfg.requestTimeout}
+    response-timeout = ${toString cfg.responseTimeout}
+
+    [:tls]
+    store = ${toString cfg.store}
+    organization = ${cfg.certOrg}
+    gen-certs = ${lib.boolToString cfg.genCerts}
+    regen-certs = ${lib.boolToString cfg.regenCerts}
+    ${lib.optionalString (cfg.certLifetime != "") "cert-lifetime = ${cfg.certLifetime}"}
+
+  '';
+  genINI = lib.generators.toINI { };
+  configFile = pkgs.writeText "config.ini" (lib.strings.concatStrings (
+    [ globalSection ] ++ (lib.lists.forEach cfg.routes (section:
+      let
+        name = section.route;
+        params = builtins.removeAttrs section [ "route" ];
+      in
+      genINI
+        {
+          "${name}" = params;
+        } + "\n"
+    ))
+  ));
+in
+{
+  options.services.stargazer = {
+    enable = lib.mkEnableOption (lib.mdDoc "Stargazer Gemini server");
+
+    listen = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      default = [ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]";
+      defaultText = lib.literalExpression ''[ "0.0.0.0" ] ++ lib.optional config.networking.enableIPv6 "[::0]"'';
+      example = lib.literalExpression ''[ "10.0.0.12" "[2002:a00:1::]" ]'';
+      description = lib.mdDoc ''
+        Address and port to listen on.
+      '';
+    };
+
+    connectionLogging = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc "Whether or not to log connections to stdout.";
+    };
+
+    ipLog = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc "Log client IP addresses in the connection log.";
+    };
+
+    ipLogPartial = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = lib.mdDoc "Log partial client IP addresses in the connection log.";
+    };
+
+    requestTimeout = lib.mkOption {
+      type = lib.types.int;
+      default = 5;
+      description = lib.mdDoc ''
+        Number of seconds to wait for the client to send a complete
+        request. Set to 0 to disable.
+      '';
+    };
+
+    responseTimeout = lib.mkOption {
+      type = lib.types.int;
+      default = 0;
+      description = lib.mdDoc ''
+        Number of seconds to wait for the client to send a complete
+        request and for stargazer to finish sending the response.
+        Set to 0 to disable.
+      '';
+    };
+
+    store = lib.mkOption {
+      type = lib.types.path;
+      default = /var/lib/gemini/certs;
+      description = lib.mdDoc ''
+        Path to the certificate store on disk. This should be a
+        persistent directory writable by Stargazer.
+      '';
+    };
+
+    certOrg = lib.mkOption {
+      type = lib.types.str;
+      default = "stargazer";
+      description = lib.mdDoc ''
+        The name of the organization responsible for the X.509
+        certificate's /O name.
+      '';
+    };
+
+    genCerts = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Set to false to disable automatic certificate generation.
+        Use if you want to provide your own certs.
+      '';
+    };
+
+    regenCerts = lib.mkOption {
+      type = lib.types.bool;
+      default = true;
+      description = lib.mdDoc ''
+        Set to false to turn off automatic regeneration of expired certificates.
+        Use if you want to provide your own certs.
+      '';
+    };
+
+    certLifetime = lib.mkOption {
+      type = lib.types.str;
+      default = "";
+      description = lib.mdDoc ''
+        How long certs generated by Stargazer should live for.
+        Certs live forever by default.
+      '';
+      example = lib.literalExpression "\"1y\"";
+    };
+
+    routes = lib.mkOption {
+      type = lib.types.listOf
+        (lib.types.submodule {
+          freeformType = with lib.types; attrsOf (nullOr
+            (oneOf [
+              bool
+              int
+              float
+              str
+            ]) // {
+            description = "INI atom (null, bool, int, float or string)";
+          });
+          options.route = lib.mkOption {
+            type = lib.types.str;
+            description = lib.mdDoc "Route section name";
+          };
+        });
+      default = [ ];
+      description = lib.mdDoc ''
+        Routes that Stargazer should server.
+
+        Expressed as a list of attribute sets. Each set must have a key `route`
+        that becomes the section name for that route in the stargazer ini cofig.
+        The remaining keys and vaules become the parameters for that route.
+
+        [Refer to upstream docs for other params](https://git.sr.ht/~zethra/stargazer/tree/main/item/doc/stargazer.ini.5.txt)
+      '';
+      example = lib.literalExpression ''
+        [
+          {
+            route = "example.com";
+            root = "/srv/gemini/example.com"
+          }
+          {
+            route = "example.com:/man";
+            root = "/cgi-bin";
+            cgi = true;
+          }
+          {
+            route = "other.org~(.*)";
+            redirect = "gemini://example.com";
+            rewrite = "\1";
+          }
+        ]
+      '';
+    };
+
+    user = lib.mkOption {
+      type = lib.types.str;
+      default = "stargazer";
+      description = lib.mdDoc "User account under which stargazer runs.";
+    };
+
+    group = lib.mkOption {
+      type = lib.types.str;
+      default = "stargazer";
+      description = lib.mdDoc "Group account under which stargazer runs.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.stargazer = {
+      description = "Stargazer gemini server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.stargazer}/bin/stargazer ${configFile}";
+        Restart = "always";
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+    # Create default cert store
+    system.activationScripts.makeStargazerCertDir =
+      lib.optionalAttrs (cfg.store == /var/lib/gemini/certs) ''
+        mkdir -p /var/lib/gemini/certs
+        chown -R ${cfg.user}:${cfg.group} /var/lib/gemini/certs
+      '';
+
+    users.users = lib.optionalAttrs (cfg.user == "stargazer") {
+      stargazer = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = lib.optionalAttrs (cfg.group == "stargazer") {
+      stargazer = { };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ gaykitty ];
+}
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
index d8bfee547c79a..4d2c36287be69 100644
--- a/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -234,11 +234,11 @@ in
           ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
         done
 
-        ${if cfg.extraConfigFiles != [] then ''
+        ${optionalString (cfg.extraConfigFiles != []) ''
           for i in ${toString cfg.extraConfigFiles}; do
             ln -sfn $i ${cfg.baseDir}/conf/`basename $i`
           done
-        '' else ""}
+        ''}
 
         # Create a modified catalina.properties file
         # Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries
@@ -345,7 +345,7 @@ in
 
           # Symlink all the given web applications files or paths into the webapps/ directory
           # of this virtual host
-          for i in "${if virtualHost ? webapps then toString virtualHost.webapps else ""}"; do
+          for i in "${optionalString (virtualHost ? webapps) (toString virtualHost.webapps)}"; do
             if [ -f $i ]; then
               # If the given web application is a file, symlink it into the webapps/ directory
               ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix
index 0aaac8a14e49d..1515779c90649 100644
--- a/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixos/modules/services/web-servers/unit/default.nix
@@ -104,7 +104,7 @@ in {
         PIDFile = "/run/unit/unit.pid";
         ExecStart = ''
           ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
-                                   --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --tmp '/tmp' \
+                                   --log '${cfg.logDir}/unit.log' --statedir '${cfg.stateDir}' --tmpdir '/tmp' \
                                    --user ${cfg.user} --group ${cfg.group}
         '';
         ExecStop = ''
diff --git a/nixos/modules/services/web-servers/varnish/default.nix b/nixos/modules/services/web-servers/varnish/default.nix
index e34c22d2868f1..d7f19be0cec47 100644
--- a/nixos/modules/services/web-servers/varnish/default.nix
+++ b/nixos/modules/services/web-servers/varnish/default.nix
@@ -99,7 +99,7 @@ in
     environment.systemPackages = [ cfg.package ];
 
     # check .vcl syntax at compile time (e.g. before nixops deployment)
-    system.extraDependencies = mkIf cfg.enableConfigCheck [
+    system.checks = mkIf cfg.enableConfigCheck [
       (pkgs.runCommand "check-varnish-syntax" {} ''
         ${cfg.package}/bin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)
       '')
diff --git a/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixos/modules/services/x11/desktop-managers/budgie.nix
index 6e6d5ef3face2..b7341d4d8b491 100644
--- a/nixos/modules/services/x11/desktop-managers/budgie.nix
+++ b/nixos/modules/services/x11/desktop-managers/budgie.nix
@@ -12,16 +12,48 @@ let
     inherit (cfg) extraGSettingsOverrides extraGSettingsOverridePackages;
     inherit nixos-background-dark nixos-background-light;
   };
+
+  nixos-background-info = pkgs.writeTextFile {
+    name = "nixos-background-info";
+    text = ''
+      <?xml version="1.0"?>
+      <!DOCTYPE wallpapers SYSTEM "gnome-wp-list.dtd">
+      <wallpapers>
+        <wallpaper deleted="false">
+          <name>Nineish</name>
+          <filename>${nixos-background-light.gnomeFilePath}</filename>
+          <options>zoom</options>
+          <shade_type>solid</shade_type>
+          <pcolor>#d1dcf8</pcolor>
+          <scolor>#e3ebfe</scolor>
+        </wallpaper>
+        <wallpaper deleted="false">
+          <name>Nineish Dark Gray</name>
+          <filename>${nixos-background-dark.gnomeFilePath}</filename>
+          <options>zoom</options>
+          <shade_type>solid</shade_type>
+          <pcolor>#151515</pcolor>
+          <scolor>#262626</scolor>
+        </wallpaper>
+      </wallpapers>
+    '';
+    destination = "/share/gnome-background-properties/nixos.xml";
+  };
 in {
   options = {
     services.xserver.desktopManager.budgie = {
-      enable = mkEnableOption (mdDoc "Budgie desktop");
+      enable = mkEnableOption (mdDoc "the Budgie desktop");
 
       sessionPath = mkOption {
-        description = mdDoc "Additional list of packages to be added to the session search path. Useful for GSettings-conditional autostart.";
-        type = with types; listOf package;
-        example = literalExpression "[ pkgs.budgie.budgie-desktop-view ]";
+        description = lib.mdDoc ''
+          Additional list of packages to be added to the session search path.
+          Useful for GSettings-conditional autostart.
+
+          Note that this should be a last resort; patching the package is preferred (see GPaste).
+        '';
+        type = types.listOf types.package;
         default = [];
+        example = literalExpression "[ pkgs.gnome.gpaste ]";
       };
 
       extraGSettingsOverrides = mkOption {
@@ -32,14 +64,21 @@ in {
 
       extraGSettingsOverridePackages = mkOption {
         description = mdDoc "List of packages for which GSettings are overridden.";
-        type = with types; listOf path;
+        type = types.listOf types.path;
         default = [];
       };
+
+      extraPlugins = mkOption {
+        description = mdDoc "Extra plugins for the Budgie desktop";
+        type = types.listOf types.package;
+        default = [];
+        example = literalExpression "[ pkgs.budgiePlugins.budgie-analogue-clock-applet ]";
+      };
     };
 
     environment.budgie.excludePackages = mkOption {
       description = mdDoc "Which packages Budgie should exclude from the default environment.";
-      type = with types; listOf package;
+      type = types.listOf types.package;
       default = [];
       example = literalExpression "[ pkgs.mate-terminal ]";
     };
@@ -76,16 +115,19 @@ in {
         # Budgie Desktop.
         budgie.budgie-backgrounds
         budgie.budgie-control-center
-        budgie.budgie-desktop
+        (budgie.budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
         budgie.budgie-desktop-view
         budgie.budgie-screensaver
 
         # Required by the Budgie Desktop session.
-        (gnome.gnome-session.override {gnomeShellSupport = false;})
+        (gnome.gnome-session.override { gnomeShellSupport = false; })
 
         # Required by Budgie Menu.
         gnome-menus
 
+        # Required by Budgie Control Center.
+        gnome.zenity
+
         # Provides `gsettings`.
         glib
 
@@ -106,6 +148,7 @@ in {
           # Desktop themes.
           qogir-theme
           qogir-icon-theme
+          nixos-background-info
 
           # Default settings.
           nixos-gsettings-overrides
diff --git a/nixos/modules/services/x11/desktop-managers/deepin.nix b/nixos/modules/services/x11/desktop-managers/deepin.nix
new file mode 100644
index 0000000000000..70be6ed7c05ea
--- /dev/null
+++ b/nixos/modules/services/x11/desktop-managers/deepin.nix
@@ -0,0 +1,208 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.deepin;
+
+  nixos-gsettings-overrides = pkgs.deepin.dde-gsettings-schemas.override {
+    extraGSettingsOverridePackages = cfg.extraGSettingsOverridePackages;
+    extraGSettingsOverrides = cfg.extraGSettingsOverrides;
+  };
+in
+{
+  options = {
+
+    services.xserver.desktopManager.deepin = {
+      enable = mkEnableOption (lib.mdDoc "Enable Deepin desktop manager");
+      extraGSettingsOverrides = mkOption {
+        default = "";
+        type = types.lines;
+        description = lib.mdDoc "Additional gsettings overrides.";
+      };
+      extraGSettingsOverridePackages = mkOption {
+        default = [ ];
+        type = types.listOf types.path;
+        description = lib.mdDoc "List of packages for which gsettings are overridden.";
+      };
+    };
+
+    environment.deepin.excludePackages = mkOption {
+      default = [ ];
+      type = types.listOf types.package;
+      description = lib.mdDoc "List of default packages to exclude from the configuration";
+    };
+
+  };
+
+  config = mkIf cfg.enable
+    {
+      services.xserver.displayManager.sessionPackages = [ pkgs.deepin.startdde ];
+      services.xserver.displayManager.defaultSession = mkDefault "deepin";
+
+      # Update the DBus activation environment after launching the desktop manager.
+      services.xserver.displayManager.sessionCommands = ''
+        ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all
+      '';
+
+      hardware.bluetooth.enable = mkDefault true;
+      hardware.pulseaudio.enable = mkDefault true;
+      security.polkit.enable = true;
+
+      services.deepin.dde-daemon.enable = mkForce true;
+      services.deepin.dde-api.enable = mkForce true;
+      services.deepin.app-services.enable = mkForce true;
+
+      services.colord.enable = mkDefault true;
+      services.accounts-daemon.enable = mkDefault true;
+      services.gvfs.enable = mkDefault true;
+      services.gnome.glib-networking.enable = mkDefault true;
+      services.gnome.gnome-keyring.enable = mkDefault true;
+      services.bamf.enable = mkDefault true;
+
+      services.xserver.libinput.enable = mkDefault true;
+      services.udisks2.enable = true;
+      services.upower.enable = mkDefault config.powerManagement.enable;
+      networking.networkmanager.enable = mkDefault true;
+      programs.dconf.enable = mkDefault true;
+
+      fonts.fonts = with pkgs; [ noto-fonts ];
+      xdg.mime.enable = true;
+      xdg.menus.enable = true;
+      xdg.icons.enable = true;
+      xdg.portal.enable = mkDefault true;
+      xdg.portal.extraPortals = mkDefault [
+        (pkgs.xdg-desktop-portal-gtk.override {
+          buildPortalsInGnome = false;
+        })
+      ];
+
+      environment.sessionVariables = {
+        NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
+        DDE_POLKIT_AGENT_PLUGINS_DIRS = [ "${pkgs.deepin.dpa-ext-gnomekeyring}/lib/polkit-1-dde/plugins" ];
+      };
+
+      environment.pathsToLink = [
+        "/lib/dde-dock/plugins"
+        "/lib/dde-control-center"
+        "/lib/dde-session-shell"
+        "/lib/dde-file-manager"
+        "/share/backgrounds"
+        "/share/wallpapers"
+      ];
+
+      environment.etc = {
+        "distribution.info".text = ''
+          [Distribution]
+          Name=NixOS
+          WebsiteName=www.nixos.org
+          Website=https://www.nixos.org
+          Logo=${pkgs.nixos-icons}/share/icons/hicolor/96x96/apps/nix-snowflake.png
+          LogoLight=${pkgs.nixos-icons}/share/icons/hicolor/32x32/apps/nix-snowflake.png
+          LogoTransparent=${pkgs.deepin.deepin-desktop-base}/share/pixmaps/distribution_logo_transparent.svg
+        '';
+        "deepin-installer.conf".text = ''
+          system_info_vendor_name="Copyright (c) 2003-2023 NixOS contributors"
+        '';
+      };
+
+      systemd.tmpfiles.rules = [
+        "d /var/lib/AccountsService 0775 root root - -"
+        "C /var/lib/AccountsService/icons 0775 root root - ${pkgs.deepin.dde-account-faces}/var/lib/AccountsService/icons"
+      ];
+
+      security.pam.services.dde-lock.text = ''
+        # original at {dde-session-shell}/etc/pam.d/dde-lock
+        auth      substack      login
+        account   include       login
+        password  substack      login
+        session   include       login
+      '';
+
+      environment.systemPackages = with pkgs; with deepin;
+        let
+          requiredPackages = [
+            pciutils # for dtkcore/startdde
+            xdotool # for dde-daemon
+            glib # for gsettings program / gdbus
+            gtk3 # for gtk-launch program
+            xdg-user-dirs # Update user dirs
+            util-linux # runuser
+            polkit_gnome
+            librsvg # dde-api use rsvg-convert
+            lshw # for dtkcore
+            libsForQt5.kde-gtk-config # deepin-api/gtk-thumbnailer need
+            libsForQt5.kglobalaccel
+            xsettingsd # lightdm-deepin-greeter
+            qt5platform-plugins
+            deepin-pw-check
+            deepin-turbo
+
+            dde-account-faces
+            deepin-icon-theme
+            deepin-sound-theme
+            deepin-gtk-theme
+            deepin-wallpapers
+
+            startdde
+            dde-dock
+            dde-launcher
+            dde-session-ui
+            dde-session-shell
+            dde-file-manager
+            dde-control-center
+            dde-network-core
+            dde-clipboard
+            dde-calendar
+            dde-polkit-agent
+            dpa-ext-gnomekeyring
+            deepin-desktop-schemas
+            deepin-terminal
+            dde-kwin
+            deepin-kwin
+          ];
+          optionalPackages = [
+            onboard # dde-dock plugin
+            deepin-camera
+            deepin-calculator
+            deepin-compressor
+            deepin-editor
+            deepin-picker
+            deepin-draw
+            deepin-album
+            deepin-image-viewer
+            deepin-music
+            deepin-movie-reborn
+            deepin-system-monitor
+            deepin-screen-recorder
+            deepin-shortcut-viewer
+          ];
+        in
+        requiredPackages
+        ++ utils.removePackagesByName optionalPackages config.environment.deepin.excludePackages;
+
+      services.dbus.packages = with pkgs.deepin; [
+        dde-dock
+        dde-launcher
+        dde-session-ui
+        dde-session-shell
+        dde-file-manager
+        dde-control-center
+        dde-calendar
+        dde-clipboard
+        dde-kwin
+        deepin-kwin
+        deepin-pw-check
+      ];
+
+      systemd.packages = with pkgs.deepin; [
+        dde-launcher
+        dde-file-manager
+        dde-calendar
+        dde-clipboard
+        deepin-kwin
+      ];
+    };
+}
+
diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index a261ca44058a0..66cb4ee29c0a9 100644
--- a/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixos/modules/services/x11/desktop-managers/default.nix
@@ -21,7 +21,7 @@ in
     ./none.nix ./xterm.nix ./phosh.nix ./xfce.nix ./plasma5.nix ./lumina.nix
     ./lxqt.nix ./enlightenment.nix ./gnome.nix ./retroarch.nix ./kodi.nix
     ./mate.nix ./pantheon.nix ./surf-display.nix ./cde.nix
-    ./cinnamon.nix ./budgie.nix
+    ./cinnamon.nix ./budgie.nix ./deepin.nix
   ];
 
   options = {
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 73a864bb95fe8..38f932ffb4206 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -429,7 +429,8 @@ in
             dolphin-plugins
             ffmpegthumbs
             kdegraphics-thumbnailers
-            pkgs.kio-admin
+            kde-inotify-survey
+            kio-admin
             kio-extras
           ];
           optionalPackages = [
diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index f77036360e029..f3391c6e11693 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -260,7 +260,9 @@ in {
   options = {
 
     services.xserver.libinput = {
-      enable = mkEnableOption (lib.mdDoc "libinput");
+      enable = mkEnableOption (lib.mdDoc "libinput") // {
+        default = true;
+      };
       mouse = mkConfigForDevice "mouse";
       touchpad = mkConfigForDevice "touchpad";
     };
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index fcc18c9a26fd0..6d2321be8ef73 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -121,7 +121,7 @@ let
           fi
         done
 
-        for i in $(find ${toString cfg.modules} -type d); do
+        for i in $(find ${toString cfg.modules} -type d | sort); do
           if test $(echo $i/*.so* | wc -w) -ne 0; then
             echo "  ModulePath \"$i\"" >> $out
           fi
@@ -776,7 +776,7 @@ in
         xorg.xf86inputevdev.out
       ];
 
-    system.extraDependencies = singleton (pkgs.runCommand "xkb-validated" {
+    system.checks = singleton (pkgs.runCommand "xkb-validated" {
       inherit (cfg) xkbModel layout xkbVariant xkbOptions;
       nativeBuildInputs = with pkgs.buildPackages; [ xkbvalidate ];
       preferLocalBuild = true;
diff --git a/nixos/modules/system/activation/bootspec.cue b/nixos/modules/system/activation/bootspec.cue
index 9f857a1b1cd80..1f7b4afa87ac4 100644
--- a/nixos/modules/system/activation/bootspec.cue
+++ b/nixos/modules/system/activation/bootspec.cue
@@ -1,4 +1,6 @@
-#V1: {
+import "struct"
+
+#BootspecV1: {
 	system:         string
 	init:           string
 	initrd?:        string
@@ -7,12 +9,23 @@
 	kernelParams: [...string]
 	label:    string
 	toplevel: string
-	specialisation?: {
-		[=~"^"]: #V1
-	}
-	extensions?: {...}
 }
 
-Document: {
-	v1: #V1
+// A restricted document does not allow any official specialisation
+// information in it to avoid "recursive specialisations".
+#RestrictedDocument: struct.MinFields(1) & {
+	"org.nixos.bootspec.v1": #BootspecV1
+	[=~"^"]:                 #BootspecExtension
+}
+
+// Specialisations are a hashmap of strings
+#BootspecSpecialisationV1: [string]: #RestrictedDocument
+
+// Bootspec extensions are defined by the extension author.
+#BootspecExtension: {...}
+
+// A "full" document allows official specialisation information
+// in the top-level with a reserved namespaced key.
+Document: #RestrictedDocument & {
+	"org.nixos.specialisation.v1"?: #BootspecSpecialisationV1
 }
diff --git a/nixos/modules/system/activation/bootspec.nix b/nixos/modules/system/activation/bootspec.nix
index 1055ed1dda902..9e1fa309d5db0 100644
--- a/nixos/modules/system/activation/bootspec.nix
+++ b/nixos/modules/system/activation/bootspec.nix
@@ -16,20 +16,20 @@ let
       filename = "boot.json";
       json =
         pkgs.writeText filename
-          (builtins.toJSON
+        (builtins.toJSON
+          # Merge extensions first to not let them shadow NixOS bootspec data.
+          (cfg.extensions //
           {
-            v1 = {
+            "org.nixos.bootspec.v1" = {
               system = config.boot.kernelPackages.stdenv.hostPlatform.system;
               kernel = "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}";
               kernelParams = config.boot.kernelParams;
               label = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} (Linux ${config.boot.kernelPackages.kernel.modDirVersion})";
-
-              inherit (cfg) extensions;
             } // lib.optionalAttrs config.boot.initrd.enable {
               initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
               initrdSecrets = "${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets";
             };
-          });
+          }));
 
       generator =
         let
@@ -40,10 +40,10 @@ let
           # This can only be done here because we *cannot* depend on $out
           # referring to the toplevel, except by living in the toplevel itself.
           toplevelInjector = lib.escapeShellArgs [
-            "${pkgs.jq}/bin/jq"
+            "${pkgs.buildPackages.jq}/bin/jq"
             ''
-              .v1.toplevel = $toplevel |
-              .v1.init = $init
+              ."org.nixos.bootspec.v1".toplevel = $toplevel |
+              ."org.nixos.bootspec.v1".init = $init
             ''
             "--sort-keys"
             "--arg" "toplevel" "${placeholder "out"}"
@@ -60,16 +60,12 @@ let
                 children);
             in
             lib.escapeShellArgs [
-              "${pkgs.jq}/bin/jq"
+              "${pkgs.buildPackages.jq}/bin/jq"
               "--sort-keys"
-              ".v1.specialisation = ($ARGS.named | map_values(. | first | .v1))"
+              ''."org.nixos.specialisation.v1" = ($ARGS.named | map_values(. | first))''
             ] + " ${lib.concatStringsSep " " specialisationLoader}";
         in
-        ''
-          mkdir -p $out/bootspec
-
-          ${toplevelInjector} | ${specialisationInjector} > $out/${filename}
-        '';
+        "${toplevelInjector} | ${specialisationInjector} > $out/${filename}";
 
       validator = pkgs.writeCueValidator ./bootspec.cue {
         document = "Document"; # Universal validator for any version as long the schema is correctly set.
@@ -79,10 +75,17 @@ let
 in
 {
   options.boot.bootspec = {
-    enable = lib.mkEnableOption (lib.mdDoc "Enable generation of RFC-0125 bootspec in $system/bootspec, e.g. /run/current-system/bootspec");
+    enable = lib.mkEnableOption (lib.mdDoc "the generation of RFC-0125 bootspec in $system/boot.json, e.g. /run/current-system/boot.json")
+      // { default = true; internal = true; };
+    enableValidation = lib.mkEnableOption (lib.mdDoc ''the validation of bootspec documents for each build.
+      This will introduce Go in the build-time closure as we are relying on [Cuelang](https://cuelang.org/) for schema validation.
+      Enable this option if you want to ascertain that your documents are correct.
+      ''
+    );
 
     extensions = lib.mkOption {
-      type = lib.types.attrsOf lib.types.attrs; # <namespace>: { ...namespace-specific fields }
+      # NOTE(RaitoBezarius): this is not enough to validate: extensions."osRelease" = drv; those are picked up by cue validation.
+      type = lib.types.attrsOf lib.types.anything; # <namespace>: { ...namespace-specific fields }
       default = { };
       description = lib.mdDoc ''
         User-defined data that extends the bootspec document.
@@ -112,15 +115,4 @@ in
       default = schemas.v1.filename;
     };
   };
-
-  config = lib.mkIf (cfg.enable) {
-    warnings = [
-      ''RFC-0125 is not merged yet, this is a feature preview of bootspec.
-        The schema is not definitive and features are not guaranteed to be stable until RFC-0125 is merged.
-        See:
-        - https://github.com/NixOS/nixpkgs/pull/172237 to track merge status in nixpkgs.
-        - https://github.com/NixOS/rfcs/pull/125 to track RFC status.
-      ''
-    ];
-  };
 }
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 74ab7070fd0fd..c28e530cdc777 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -82,7 +82,8 @@ let
 
       ${optionalString (!config.boot.isContainer && config.boot.bootspec.enable) ''
         ${config.boot.bootspec.writer}
-        ${config.boot.bootspec.validator} "$out/${config.boot.bootspec.filename}"
+        ${optionalString config.boot.bootspec.enableValidation
+          ''${config.boot.bootspec.validator} "$out/${config.boot.bootspec.filename}"''}
       ''}
 
       ${config.system.extraSystemBuilderCmds}
@@ -262,8 +263,23 @@ in
       default = [];
       description = lib.mdDoc ''
         A list of packages that should be included in the system
-        closure but not otherwise made available to users. This is
-        primarily used by the installation tests.
+        closure but generally not visible to users.
+
+        This option has also been used for build-time checks, but the
+        `system.checks` option is more appropriate for that purpose as checks
+        should not leave a trace in the built system configuration.
+      '';
+    };
+
+    system.checks = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      description = lib.mdDoc ''
+        Packages that are added as dependencies of the system's build, usually
+        for the purpose of validating some part of the configuration.
+
+        Unlike `system.extraDependencies`, these store paths do not
+        become part of the built system configuration.
       '';
     };
 
@@ -362,7 +378,17 @@ in
           fi
         '';
 
-    system.systemBuilderArgs = lib.optionalAttrs (config.system.forbiddenDependenciesRegex != "") {
+    system.systemBuilderArgs = {
+      # Not actually used in the builder. `passedChecks` is just here to create
+      # the build dependencies. Checks are similar to build dependencies in the
+      # sense that if they fail, the system build fails. However, checks do not
+      # produce any output of value, so they are not used by the system builder.
+      # In fact, using them runs the risk of accidentally adding unneeded paths
+      # to the system closure, which defeats the purpose of the `system.checks`
+      # option, as opposed to `system.extraDependencies`.
+      passedChecks = concatStringsSep " " config.system.checks;
+    }
+    // lib.optionalAttrs (config.system.forbiddenDependenciesRegex != "") {
       inherit (config.system) forbiddenDependenciesRegex;
       closureInfo = pkgs.closureInfo { rootPaths = [
         # override to avoid  infinite recursion (and to allow using extraDependencies to add forbidden dependencies)
@@ -370,6 +396,7 @@ in
       ]; };
     };
 
+
     system.build.toplevel = if config.system.includeBuildDependencies then systemWithBuildDeps else system;
 
   };
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index cceb02c1a73b3..b003d983d2be2 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -125,6 +125,10 @@ let
       magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
       mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
     };
+    loongarch64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02\x01'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\xfc\x00\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
     wasm32-wasi = {
       magicOrExtension = ''\x00asm'';
       mask = ''\xff\xff\xff\xff'';
diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix
index 034b2b9906f55..a2764187a5333 100644
--- a/nixos/modules/system/boot/grow-partition.nix
+++ b/nixos/modules/system/boot/grow-partition.nix
@@ -17,6 +17,11 @@ with lib;
 
   config = mkIf config.boot.growPartition {
 
+    assertions = [{
+      assertion = !config.boot.initrd.systemd.enable;
+      message = "systemd stage 1 does not support 'boot.growPartition' yet.";
+    }];
+
     boot.initrd.extraUtilsCommands = ''
       copy_bin_and_libs ${pkgs.gawk}/bin/gawk
       copy_bin_and_libs ${pkgs.gnused}/bin/sed
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
index a1017c3e24204..e8bbf1d040329 100644
--- a/nixos/modules/system/boot/initrd-network.nix
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -67,11 +67,15 @@ in
 
     boot.initrd.network.flushBeforeStage2 = mkOption {
       type = types.bool;
-      default = true;
+      default = !config.boot.initrd.systemd.enable;
+      defaultText = "!config.boot.initrd.systemd.enable";
       description = lib.mdDoc ''
         Whether to clear the configuration of the interfaces that were set up in
         the initrd right before stage 2 takes over. Stage 2 will do the regular network
         configuration based on the NixOS networking options.
+
+        The default is false when systemd is enabled in initrd,
+        because the systemd-networkd documentation suggests it.
       '';
     };
 
diff --git a/nixos/modules/system/boot/initrd-openvpn.nix b/nixos/modules/system/boot/initrd-openvpn.nix
index cbc61d55d6bb3..2530240628e42 100644
--- a/nixos/modules/system/boot/initrd-openvpn.nix
+++ b/nixos/modules/system/boot/initrd-openvpn.nix
@@ -51,7 +51,7 @@ in
 
     # Add openvpn and ip binaries to the initrd
     # The shared libraries are required for DNS resolution
-    boot.initrd.extraUtilsCommands = ''
+    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       copy_bin_and_libs ${pkgs.openvpn}/bin/openvpn
       copy_bin_and_libs ${pkgs.iproute2}/bin/ip
 
@@ -59,18 +59,33 @@ in
       cp -pv ${pkgs.glibc}/lib/libnss_dns.so.2 $out/lib
     '';
 
+    boot.initrd.systemd.storePaths = [
+      "${pkgs.openvpn}/bin/openvpn"
+      "${pkgs.iproute2}/bin/ip"
+      "${pkgs.glibc}/lib/libresolv.so.2"
+      "${pkgs.glibc}/lib/libnss_dns.so.2"
+    ];
+
     boot.initrd.secrets = {
       "/etc/initrd.ovpn" = cfg.configuration;
     };
 
     # openvpn --version would exit with 1 instead of 0
-    boot.initrd.extraUtilsCommandsTest = ''
+    boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
       $out/bin/openvpn --show-gateway
     '';
 
-    boot.initrd.network.postCommands = ''
+    boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       openvpn /etc/initrd.ovpn &
     '';
+
+    boot.initrd.systemd.services.openvpn = {
+      wantedBy = [ "initrd.target" ];
+      path = [ pkgs.iproute2 ];
+      after = [ "network.target" "initrd-nixos-copy-secrets.service" ];
+      serviceConfig.ExecStart = "${pkgs.openvpn}/bin/openvpn /etc/initrd.ovpn";
+      serviceConfig.Type = "notify";
+    };
   };
 
 }
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 125f75d667069..60c5ff62ffff0 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -5,6 +5,10 @@ with lib;
 let
 
   cfg = config.boot.initrd.network.ssh;
+  shell = if cfg.shell == null then "/bin/ash" else cfg.shell;
+  inherit (config.programs.ssh) package;
+
+  enabled = let initrd = config.boot.initrd; in (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable;
 
 in
 
@@ -33,8 +37,9 @@ in
     };
 
     shell = mkOption {
-      type = types.str;
-      default = "/bin/ash";
+      type = types.nullOr types.str;
+      default = null;
+      defaultText = ''"/bin/ash"'';
       description = lib.mdDoc ''
         Login shell of the remote user. Can be used to limit actions user can do.
       '';
@@ -119,9 +124,11 @@ in
     sshdCfg = config.services.openssh;
 
     sshdConfig = ''
+      UsePAM no
       Port ${toString cfg.port}
 
       PasswordAuthentication no
+      AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u
       ChallengeResponseAuthentication no
 
       ${flip concatMapStrings cfg.hostKeys (path: ''
@@ -142,7 +149,7 @@ in
 
       ${cfg.extraConfig}
     '';
-  in mkIf (config.boot.initrd.network.enable && cfg.enable) {
+  in mkIf enabled {
     assertions = [
       {
         assertion = cfg.authorizedKeys != [];
@@ -157,14 +164,19 @@ in
           for instructions.
         '';
       }
+
+      {
+        assertion = config.boot.initrd.systemd.enable -> cfg.shell == null;
+        message = "systemd stage 1 does not support boot.initrd.network.ssh.shell";
+      }
     ];
 
-    boot.initrd.extraUtilsCommands = ''
-      copy_bin_and_libs ${pkgs.openssh}/bin/sshd
+    boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+      copy_bin_and_libs ${package}/bin/sshd
       cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
     '';
 
-    boot.initrd.extraUtilsCommandsTest = ''
+    boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
       # sshd requires a host key to check config, so we pass in the test's
       tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)"
       cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey"
@@ -176,9 +188,9 @@ in
       rm "$tmpkey"
     '';
 
-    boot.initrd.network.postCommands = ''
-      echo '${cfg.shell}' > /etc/shells
-      echo 'root:x:0:0:root:/root:${cfg.shell}' > /etc/passwd
+    boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) ''
+      echo '${shell}' > /etc/shells
+      echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd
       echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
       echo 'passwd: files' > /etc/nsswitch.conf
 
@@ -204,7 +216,7 @@ in
       /bin/sshd -e
     '';
 
-    boot.initrd.postMountCommands = ''
+    boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) ''
       # Stop sshd cleanly before stage 2.
       #
       # If you want to keep it around to debug post-mount SSH issues,
@@ -217,6 +229,38 @@ in
 
     boot.initrd.secrets = listToAttrs
       (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys);
+
+    # Systemd initrd stuff
+    boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable {
+      users.sshd = { uid = 1; group = "sshd"; };
+      groups.sshd = { gid = 1; };
+
+      contents."/etc/ssh/authorized_keys.d/root".text =
+        concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys;
+      contents."/etc/ssh/sshd_config".text = sshdConfig;
+      storePaths = ["${package}/bin/sshd"];
+
+      services.sshd = {
+        description = "SSH Daemon";
+        wantedBy = ["initrd.target"];
+        after = ["network.target" "initrd-nixos-copy-secrets.service"];
+
+        # Keys from Nix store are world-readable, which sshd doesn't
+        # like. If this were a real nix store and not the initrd, we
+        # neither would nor could do this
+        preStart = flip concatMapStrings cfg.hostKeys (path: ''
+          /bin/chmod 0600 "${initrdKeyPath path}"
+        '');
+        unitConfig.DefaultDependencies = false;
+        serviceConfig = {
+          ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config";
+          Type = "simple";
+          KillMode = "process";
+          Restart = "on-failure";
+        };
+      };
+    };
+
   };
 
 }
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 121d7e88e74de..9f80b40d116c6 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, options, lib, pkgs, ... }:
 
 with lib;
 
@@ -12,13 +12,8 @@ let
     # Package set of targeted architecture
     if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs;
 
-  realGrub = if cfg.version == 1 then grubPkgs.grub
-    else if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
-    else if cfg.trustedBoot.enable
-         then if cfg.trustedBoot.isHPLaptop
-              then grubPkgs.trustedGrub-for-HP
-              else grubPkgs.trustedGrub
-         else grubPkgs.grub2;
+  realGrub = if cfg.zfsSupport then grubPkgs.grub2.override { zfsSupport = true; }
+    else grubPkgs.grub2;
 
   grub =
     # Don't include GRUB if we're only generating a GRUB menu (e.g.,
@@ -28,12 +23,11 @@ let
     else realGrub;
 
   grubEfi =
-    # EFI version of Grub v2
-    if cfg.efiSupport && (cfg.version == 2)
+    if cfg.efiSupport
     then realGrub.override { efiSupport = cfg.efiSupport; }
     else null;
 
-  f = x: if x == null then "" else "" + x;
+  f = x: optionalString (x != null) ("" + x);
 
   grubConfig = args:
     let
@@ -52,24 +46,24 @@ let
       fullName = lib.getName realGrub;
       fullVersion = lib.getVersion realGrub;
       grubEfi = f grubEfi;
-      grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f (grubEfi.grubTarget or "") else "";
+      grubTargetEfi = optionalString cfg.efiSupport (f (grubEfi.grubTarget or ""));
       bootPath = args.path;
       storePath = config.boot.loader.grub.storePath;
       bootloaderId = if args.efiBootloaderId == null then "${config.system.nixos.distroName}${efiSysMountPoint'}" else args.efiBootloaderId;
       timeout = if config.boot.loader.timeout == null then -1 else config.boot.loader.timeout;
-      users = if cfg.users == {} || cfg.version != 1 then cfg.users else throw "GRUB version 1 does not support user accounts.";
       theme = f cfg.theme;
       inherit efiSysMountPoint;
       inherit (args) devices;
       inherit (efi) canTouchEfiVariables;
       inherit (cfg)
-        version extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
+        extraConfig extraPerEntryConfig extraEntries forceInstall useOSProber
         extraGrubInstallArgs
         extraEntriesBeforeNixOS extraPrepareConfig configurationLimit copyKernels
-        default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios;
+        default fsIdentifier efiSupport efiInstallAsRemovable gfxmodeEfi gfxmodeBios gfxpayloadEfi gfxpayloadBios
+        users;
       path = with pkgs; makeBinPath (
         [ coreutils gnused gnugrep findutils diffutils btrfs-progs util-linux mdadm ]
-        ++ optional (cfg.efiSupport && (cfg.version == 2)) efibootmgr
+        ++ optional cfg.efiSupport efibootmgr
         ++ optionals cfg.useOSProber [ busybox os-prober ]);
       font = if cfg.font == null then ""
         else (if lib.last (lib.splitString "." cfg.font) == "pf2"
@@ -109,14 +103,8 @@ in
       };
 
       version = mkOption {
-        default = 2;
-        example = 1;
+        visible = false;
         type = types.int;
-        description = lib.mdDoc ''
-          The version of GRUB to use: `1` for GRUB
-          Legacy (versions 0.9x), or `2` (the
-          default) for GRUB 2.
-        '';
       };
 
       device = mkOption {
@@ -682,39 +670,6 @@ in
         '';
       };
 
-      trustedBoot = {
-
-        enable = mkOption {
-          default = false;
-          type = types.bool;
-          description = lib.mdDoc ''
-            Enable trusted boot. GRUB will measure all critical components during
-            the boot process to offer TCG (TPM) support.
-          '';
-        };
-
-        systemHasTPM = mkOption {
-          default = "";
-          example = "YES_TPM_is_activated";
-          type = types.str;
-          description = lib.mdDoc ''
-            Assertion that the target system has an activated TPM. It is a safety
-            check before allowing the activation of 'trustedBoot.enable'. TrustedBoot
-            WILL FAIL TO BOOT YOUR SYSTEM if no TPM is available.
-          '';
-        };
-
-        isHPLaptop = mkOption {
-          default = false;
-          type = types.bool;
-          description = lib.mdDoc ''
-            Use a special version of TrustedGRUB that is needed by some HP laptops
-            and works only for the HP laptops.
-          '';
-        };
-
-      };
-
     };
 
   };
@@ -724,14 +679,7 @@ in
 
   config = mkMerge [
 
-    { boot.loader.grub.splashImage = mkDefault (
-        if cfg.version == 1 then pkgs.fetchurl {
-          url = "http://www.gnome-look.org/CONTENT/content-files/36909-soft-tux.xpm.gz";
-          sha256 = "14kqdx2lfqvh40h6fjjzqgff1mwk74dmbjvmqphi6azzra7z8d59";
-        }
-        # GRUB 1.97 doesn't support gzipped XPMs.
-        else defaultSplash);
-    }
+    { boot.loader.grub.splashImage = mkDefault defaultSplash; }
 
     (mkIf (cfg.splashImage == defaultSplash) {
       boot.loader.grub.backgroundColor = mkDefault "#2F302F";
@@ -789,10 +737,6 @@ in
 
       assertions = [
         {
-          assertion = !cfg.zfsSupport || cfg.version == 2;
-          message = "Only GRUB version 2 provides ZFS support";
-        }
-        {
           assertion = cfg.mirroredBoots != [ ];
           message = "You must set the option ‘boot.loader.grub.devices’ or "
             + "'boot.loader.grub.mirroredBoots' to make the system bootable.";
@@ -802,22 +746,6 @@ in
           message = "You cannot have duplicated devices in mirroredBoots";
         }
         {
-          assertion = !cfg.trustedBoot.enable || cfg.version == 2;
-          message = "Trusted GRUB is only available for GRUB 2";
-        }
-        {
-          assertion = !cfg.efiSupport || !cfg.trustedBoot.enable;
-          message = "Trusted GRUB does not have EFI support";
-        }
-        {
-          assertion = !cfg.zfsSupport || !cfg.trustedBoot.enable;
-          message = "Trusted GRUB does not have ZFS support";
-        }
-        {
-          assertion = !cfg.trustedBoot.enable || cfg.trustedBoot.systemHasTPM == "YES_TPM_is_activated";
-          message = "Trusted GRUB can break the system! Confirm that the system has an activated TPM by setting 'systemHasTPM'.";
-        }
-        {
           assertion = cfg.efiInstallAsRemovable -> cfg.efiSupport;
           message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn on boot.loader.grub.efiSupport";
         }
@@ -825,6 +753,10 @@ in
           assertion = cfg.efiInstallAsRemovable -> !config.boot.loader.efi.canTouchEfiVariables;
           message = "If you wish to to use boot.loader.grub.efiInstallAsRemovable, then turn off boot.loader.efi.canTouchEfiVariables";
         }
+        {
+          assertion = !(options.boot.loader.grub.version.isDefined && cfg.version == 1);
+          message = "Support for version 0.9x of GRUB was removed after being unsupported upstream for around a decade";
+        }
       ] ++ flip concatMap cfg.mirroredBoots (args: [
         {
           assertion = args.devices != [ ];
@@ -844,6 +776,11 @@ in
       }));
     })
 
+    (mkIf options.boot.loader.grub.version.isDefined {
+      warnings = [ ''
+        The boot.loader.grub.version option does not have any effect anymore, please remove it from your configuration.
+      '' ];
+    })
   ];
 
 
@@ -855,6 +792,10 @@ in
       (mkRenamedOptionModule [ "boot" "grubDevice" ] [ "boot" "loader" "grub" "device" ])
       (mkRenamedOptionModule [ "boot" "bootMount" ] [ "boot" "loader" "grub" "bootDevice" ])
       (mkRenamedOptionModule [ "boot" "grubSplashImage" ] [ "boot" "loader" "grub" "splashImage" ])
+      (mkRemovedOptionModule [ "boot" "loader" "grub" "trustedBoot" ] ''
+        Support for Trusted GRUB has been removed, because the project
+        has been retired upstream.
+      '')
       (mkRemovedOptionModule [ "boot" "loader" "grub" "extraInitrd" ] ''
         This option has been replaced with the bootloader agnostic
         boot.initrd.secrets option. To migrate to the initrd secrets system,
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl
index aea426c7fdf24..cfccb93264bfd 100644
--- a/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -34,28 +34,33 @@ sub getList {
 }
 
 sub readFile {
-    my ($fn) = @_; local $/ = undef;
-    open FILE, "<$fn" or return undef; my $s = <FILE>; close FILE;
-    local $/ = "\n"; chomp $s; return $s;
+    my ($fn) = @_;
+    # enable slurp mode: read entire file in one go
+    local $/ = undef;
+    open my $fh, "<$fn" or return undef;
+    my $s = <$fh>;
+    close $fh;
+    # disable slurp mode
+    local $/ = "\n";
+    chomp $s;
+    return $s;
 }
 
 sub writeFile {
     my ($fn, $s) = @_;
-    open FILE, ">$fn" or die "cannot create $fn: $!\n";
-    print FILE $s or die;
-    close FILE or die;
+    open my $fh, ">$fn" or die "cannot create $fn: $!\n";
+    print $fh $s or die "cannot write to $fn: $!\n";
+    close $fh or die "cannot close $fn: $!\n";
 }
 
 sub runCommand {
-    my ($cmd) = @_;
-    open FILE, "$cmd 2>/dev/null |" or die "Failed to execute: $cmd\n";
-    my @ret = <FILE>;
-    close FILE;
+    open(my $fh, "-|", @_) or die "Failed to execute: $@_\n";
+    my @ret = $fh->getlines();
+    close $fh;
     return ($?, @ret);
 }
 
 my $grub = get("grub");
-my $grubVersion = int(get("version"));
 my $grubTarget = get("grubTarget");
 my $extraConfig = get("extraConfig");
 my $extraPrepareConfig = get("extraPrepareConfig");
@@ -90,9 +95,7 @@ my $theme = get("theme");
 my $saveDefault = $defaultEntry eq "saved";
 $ENV{'PATH'} = get("path");
 
-die "unsupported GRUB version\n" if $grubVersion != 1 && $grubVersion != 2;
-
-print STDERR "updating GRUB $grubVersion menu...\n";
+print STDERR "updating GRUB 2 menu...\n";
 
 mkpath("$bootPath/grub", 0, 0700);
 
@@ -170,76 +173,74 @@ sub GrubFs {
     }
     my $search = "";
 
-    if ($grubVersion > 1) {
-        # ZFS is completely separate logic as zpools are always identified by a label
-        # or custom UUID
-        if ($fs->type eq 'zfs') {
-            my $sid = index($fs->device, '/');
-
-            if ($sid < 0) {
-                $search = '--label ' . $fs->device;
-                $path = '/@' . $path;
-            } else {
-                $search = '--label ' . substr($fs->device, 0, $sid);
-                $path = '/' . substr($fs->device, $sid) . '/@' . $path;
+    # ZFS is completely separate logic as zpools are always identified by a label
+    # or custom UUID
+    if ($fs->type eq 'zfs') {
+        my $sid = index($fs->device, '/');
+
+        if ($sid < 0) {
+            $search = '--label ' . $fs->device;
+            $path = '/@' . $path;
+        } else {
+            $search = '--label ' . substr($fs->device, 0, $sid);
+            $path = '/' . substr($fs->device, $sid) . '/@' . $path;
+        }
+    } else {
+        my %types = ('uuid' => '--fs-uuid', 'label' => '--label');
+
+        if ($fsIdentifier eq 'provided') {
+            # If the provided dev is identifying the partition using a label or uuid,
+            # we should get the label / uuid and do a proper search
+            my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/;
+            if ($#matches > 1) {
+                die "Too many matched devices"
+            } elsif ($#matches == 1) {
+                $search = "$types{$matches[0]} $matches[1]"
             }
         } else {
-            my %types = ('uuid' => '--fs-uuid', 'label' => '--label');
-
-            if ($fsIdentifier eq 'provided') {
-                # If the provided dev is identifying the partition using a label or uuid,
-                # we should get the label / uuid and do a proper search
-                my @matches = $fs->device =~ m/\/dev\/disk\/by-(label|uuid)\/(.*)/;
-                if ($#matches > 1) {
-                    die "Too many matched devices"
-                } elsif ($#matches == 1) {
-                    $search = "$types{$matches[0]} $matches[1]"
-                }
-            } else {
-                # Determine the identifying type
-                $search = $types{$fsIdentifier} . ' ';
+            # Determine the identifying type
+            $search = $types{$fsIdentifier} . ' ';
 
-                # Based on the type pull in the identifier from the system
-                my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid -o export @{[$fs->device]}");
-                if ($status != 0) {
-                    die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}";
-                }
-                my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/;
-                if ($#matches != 0) {
-                    die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n"
-                }
-                $search .= $matches[0];
+            # Based on the type pull in the identifier from the system
+            my ($status, @devInfo) = runCommand("@utillinux@/bin/blkid", "-o", "export", @{[$fs->device]});
+            if ($status != 0) {
+                die "Failed to get blkid info (returned $status) for @{[$fs->mount]} on @{[$fs->device]}";
+            }
+            my @matches = join("", @devInfo) =~ m/@{[uc $fsIdentifier]}=([^\n]*)/;
+            if ($#matches != 0) {
+                die "Couldn't find a $types{$fsIdentifier} for @{[$fs->device]}\n"
             }
+            $search .= $matches[0];
+        }
 
-            # BTRFS is a special case in that we need to fix the referrenced path based on subvolumes
-            if ($fs->type eq 'btrfs') {
-                my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs subvol show @{[$fs->mount]}");
+        # BTRFS is a special case in that we need to fix the referrenced path based on subvolumes
+        if ($fs->type eq 'btrfs') {
+            my ($status, @id_info) = runCommand("@btrfsprogs@/bin/btrfs", "subvol", "show", @{[$fs->mount]});
+            if ($status != 0) {
+                die "Failed to retrieve subvolume info for @{[$fs->mount]}\n";
+            }
+            my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
+            if ($#ids > 0) {
+                die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
+            } elsif ($#ids == 0) {
+                my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs", "subvol", "list", @{[$fs->mount]});
                 if ($status != 0) {
-                    die "Failed to retrieve subvolume info for @{[$fs->mount]}\n";
+                    die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n";
                 }
-                my @ids = join("\n", @id_info) =~ m/^(?!\/\n).*Subvolume ID:[ \t\n]*([0-9]+)/s;
-                if ($#ids > 0) {
-                    die "Btrfs subvol name for @{[$fs->device]} listed multiple times in mount\n"
-                } elsif ($#ids == 0) {
-                    my ($status, @path_info) = runCommand("@btrfsprogs@/bin/btrfs subvol list @{[$fs->mount]}");
-                    if ($status != 0) {
-                        die "Failed to find @{[$fs->mount]} subvolume id from btrfs\n";
-                    }
-                    my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
-                    if ($#paths > 0) {
-                        die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n";
-                    } elsif ($#paths != 0) {
-                        die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n";
-                    }
-                    $path = "/$paths[0]$path";
+                my @paths = join("", @path_info) =~ m/ID $ids[0] [^\n]* path ([^\n]*)/;
+                if ($#paths > 0) {
+                    die "Btrfs returned multiple paths for a single subvolume id, mountpoint @{[$fs->mount]}\n";
+                } elsif ($#paths != 0) {
+                    die "Btrfs did not return a path for the subvolume at @{[$fs->mount]}\n";
                 }
+                $path = "/$paths[0]$path";
             }
         }
-        if (not $search eq "") {
-            $search = "search --set=drive$driveid " . $search;
-            $path = "(\$drive$driveid)$path";
-            $driveid += 1;
-        }
+    }
+    if (not $search eq "") {
+        $search = "search --set=drive$driveid " . $search;
+        $path = "(\$drive$driveid)$path";
+        $driveid += 1;
     }
     return Grub->new(path => $path, search => $search);
 }
@@ -252,166 +253,151 @@ if ($copyKernels == 0) {
 # Generate the header.
 my $conf .= "# Automatically generated.  DO NOT EDIT THIS FILE!\n";
 
-if ($grubVersion == 1) {
-    # $defaultEntry might be "saved", indicating that we want to use the last selected configuration as default.
-    # Incidentally this is already the correct value for the grub 1 config to achieve this behaviour.
-    $conf .= "
-        default $defaultEntry
-        timeout $timeout
-    ";
-    if ($splashImage) {
-        copy $splashImage, "$bootPath/background.xpm.gz" or die "cannot copy $splashImage to $bootPath: $!\n";
-        $conf .= "splashimage " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background.xpm.gz\n";
+my @users = ();
+foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) {
+    my $name = $user->findvalue('@name') or die;
+    my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value');
+    my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value');
+    my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value');
+    my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value');
+
+    if ($hashedPasswordFile) {
+        open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!";
+        $hashedPassword = <$f>;
+        chomp $hashedPassword;
+    }
+    if ($passwordFile) {
+        open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!";
+        $password = <$f>;
+        chomp $password;
     }
-}
-
-else {
-    my @users = ();
-    foreach my $user ($dom->findnodes('/expr/attrs/attr[@name = "users"]/attrs/attr')) {
-        my $name = $user->findvalue('@name') or die;
-        my $hashedPassword = $user->findvalue('./attrs/attr[@name = "hashedPassword"]/string/@value');
-        my $hashedPasswordFile = $user->findvalue('./attrs/attr[@name = "hashedPasswordFile"]/string/@value');
-        my $password = $user->findvalue('./attrs/attr[@name = "password"]/string/@value');
-        my $passwordFile = $user->findvalue('./attrs/attr[@name = "passwordFile"]/string/@value');
-
-        if ($hashedPasswordFile) {
-            open(my $f, '<', $hashedPasswordFile) or die "Can't read file '$hashedPasswordFile'!";
-            $hashedPassword = <$f>;
-            chomp $hashedPassword;
-        }
-        if ($passwordFile) {
-            open(my $f, '<', $passwordFile) or die "Can't read file '$passwordFile'!";
-            $password = <$f>;
-            chomp $password;
-        }
 
-        if ($hashedPassword) {
-            if (index($hashedPassword, "grub.pbkdf2.") == 0) {
-                $conf .= "\npassword_pbkdf2 $name $hashedPassword";
-            }
-            else {
-                die "Password hash for GRUB user '$name' is not valid!";
-            }
-        }
-        elsif ($password) {
-            $conf .= "\npassword $name $password";
+    if ($hashedPassword) {
+        if (index($hashedPassword, "grub.pbkdf2.") == 0) {
+            $conf .= "\npassword_pbkdf2 $name $hashedPassword";
         }
         else {
-            die "GRUB user '$name' has no password!";
+            die "Password hash for GRUB user '$name' is not valid!";
         }
-        push(@users, $name);
     }
-    if (@users) {
-        $conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n";
+    elsif ($password) {
+        $conf .= "\npassword $name $password";
     }
-
-    if ($copyKernels == 0) {
-        $conf .= "
-            " . $grubStore->search;
+    else {
+        die "GRUB user '$name' has no password!";
     }
-    # FIXME: should use grub-mkconfig.
-    my $defaultEntryText = $defaultEntry;
-    if ($saveDefault) {
-        $defaultEntryText = "\"\${saved_entry}\"";
-    }
-    $conf .= "
-        " . $grubBoot->search . "
-        if [ -s \$prefix/grubenv ]; then
-          load_env
-        fi
+    push(@users, $name);
+}
+if (@users) {
+    $conf .= "\nset superusers=\"" . join(' ',@users) . "\"\n";
+}
 
-        # ‘grub-reboot’ sets a one-time saved entry, which we process here and
-        # then delete.
-        if [ \"\${next_entry}\" ]; then
-          set default=\"\${next_entry}\"
-          set next_entry=
-          save_env next_entry
-          set timeout=1
-          set boot_once=true
-        else
-          set default=$defaultEntryText
-          set timeout=$timeout
+if ($copyKernels == 0) {
+    $conf .= "
+        " . $grubStore->search;
+}
+# FIXME: should use grub-mkconfig.
+my $defaultEntryText = $defaultEntry;
+if ($saveDefault) {
+    $defaultEntryText = "\"\${saved_entry}\"";
+}
+$conf .= "
+    " . $grubBoot->search . "
+    if [ -s \$prefix/grubenv ]; then
+      load_env
+    fi
+
+    # ‘grub-reboot’ sets a one-time saved entry, which we process here and
+    # then delete.
+    if [ \"\${next_entry}\" ]; then
+      set default=\"\${next_entry}\"
+      set next_entry=
+      save_env next_entry
+      set timeout=1
+      set boot_once=true
+    else
+      set default=$defaultEntryText
+      set timeout=$timeout
+    fi
+
+    function savedefault {
+        if [ -z \"\${boot_once}\"]; then
+        saved_entry=\"\${chosen}\"
+        save_env saved_entry
         fi
+    }
 
-        function savedefault {
-            if [ -z \"\${boot_once}\"]; then
-            saved_entry=\"\${chosen}\"
-            save_env saved_entry
-            fi
-        }
-
-        # Setup the graphics stack for bios and efi systems
-        if [ \"\${grub_platform}\" = \"efi\" ]; then
-          insmod efi_gop
-          insmod efi_uga
-        else
-          insmod vbe
+    # Setup the graphics stack for bios and efi systems
+    if [ \"\${grub_platform}\" = \"efi\" ]; then
+      insmod efi_gop
+      insmod efi_uga
+    else
+      insmod vbe
+    fi
+";
+
+if ($font) {
+    copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n";
+    $conf .= "
+        insmod font
+        if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
+          insmod gfxterm
+          if [ \"\${grub_platform}\" = \"efi\" ]; then
+            set gfxmode=$gfxmodeEfi
+            set gfxpayload=$gfxpayloadEfi
+          else
+            set gfxmode=$gfxmodeBios
+            set gfxpayload=$gfxpayloadBios
+          fi
+          terminal_output gfxterm
         fi
     ";
-
-    if ($font) {
-        copy $font, "$bootPath/converted-font.pf2" or die "cannot copy $font to $bootPath: $!\n";
-        $conf .= "
-            insmod font
-            if loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/converted-font.pf2; then
-              insmod gfxterm
-              if [ \"\${grub_platform}\" = \"efi\" ]; then
-                set gfxmode=$gfxmodeEfi
-                set gfxpayload=$gfxpayloadEfi
-              else
-                set gfxmode=$gfxmodeBios
-                set gfxpayload=$gfxpayloadBios
-              fi
-              terminal_output gfxterm
-            fi
-        ";
+}
+if ($splashImage) {
+    # Keeps the image's extension.
+    my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$");
+    # The module for jpg is jpeg.
+    if ($suffix eq ".jpg") {
+        $suffix = ".jpeg";
     }
-    if ($splashImage) {
-        # Keeps the image's extension.
-        my ($filename, $dirs, $suffix) = fileparse($splashImage, qr"\..[^.]*$");
-        # The module for jpg is jpeg.
-        if ($suffix eq ".jpg") {
-            $suffix = ".jpeg";
-        }
-        if ($backgroundColor) {
-            $conf .= "
-            background_color '$backgroundColor'
-            ";
-        }
-        copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n";
+    if ($backgroundColor) {
         $conf .= "
-            insmod " . substr($suffix, 1) . "
-            if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
-              set color_normal=white/black
-              set color_highlight=black/white
-            else
-              set menu_color_normal=cyan/blue
-              set menu_color_highlight=white/blue
-            fi
+        background_color '$backgroundColor'
         ";
     }
+    copy $splashImage, "$bootPath/background$suffix" or die "cannot copy $splashImage to $bootPath: $!\n";
+    $conf .= "
+        insmod " . substr($suffix, 1) . "
+        if background_image --mode '$splashMode' " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/background$suffix; then
+          set color_normal=white/black
+          set color_highlight=black/white
+        else
+          set menu_color_normal=cyan/blue
+          set menu_color_highlight=white/blue
+        fi
+    ";
+}
 
-    rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme";
+rmtree("$bootPath/theme") or die "cannot clean up theme folder in $bootPath\n" if -e "$bootPath/theme";
 
-    if ($theme) {
-        # Copy theme
-        rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
-        $conf .= "
-            # Sets theme.
-            set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
-            export theme
-            # Load theme fonts, if any
-        ";
+if ($theme) {
+    # Copy theme
+    rcopy($theme, "$bootPath/theme") or die "cannot copy $theme to $bootPath\n";
+    $conf .= "
+        # Sets theme.
+        set theme=" . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/theme.txt
+        export theme
+        # Load theme fonts, if any
+    ";
 
-        find( { wanted => sub {
-            if ($_ =~ /\.pf2$/i) {
-                $font = File::Spec->abs2rel($File::Find::name, $theme);
-                $conf .= "
-                    loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
-                ";
-            }
-        }, no_chdir => 1 }, $theme );
-    }
+    find( { wanted => sub {
+        if ($_ =~ /\.pf2$/i) {
+            $font = File::Spec->abs2rel($File::Find::name, $theme);
+            $conf .= "
+                loadfont " . ($grubBoot->path eq "/" ? "" : $grubBoot->path) . "/theme/$font
+            ";
+        }
+    }, no_chdir => 1 }, $theme );
 }
 
 $conf .= "$extraConfig\n";
@@ -488,31 +474,19 @@ sub addEntry {
         readFile("$path/kernel-params");
     my $xenParams = $xen && -e "$path/xen-params" ? readFile("$path/xen-params") : "";
 
-    if ($grubVersion == 1) {
-        $conf .= "title $name\n";
-        $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
-        $conf .= "  kernel $xen $xenParams\n" if $xen;
-        $conf .= "  " . ($xen ? "module" : "kernel") . " $kernel $kernelParams\n";
-        $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n";
-        if ($saveDefault) {
-            $conf .= "  savedefault\n";
-        }
-        $conf .= "\n";
-    } else {
-        $conf .= "menuentry \"$name\" " . $options . " {\n";
-        if ($saveDefault) {
-            $conf .= "  savedefault\n";
-        }
-        $conf .= $grubBoot->search . "\n";
-        if ($copyKernels == 0) {
-            $conf .= $grubStore->search . "\n";
-        }
-        $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
-        $conf .= "  multiboot $xen $xenParams\n" if $xen;
-        $conf .= "  " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
-        $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n";
-        $conf .= "}\n\n";
+    $conf .= "menuentry \"$name\" " . $options . " {\n";
+    if ($saveDefault) {
+        $conf .= "  savedefault\n";
     }
+    $conf .= $grubBoot->search . "\n";
+    if ($copyKernels == 0) {
+        $conf .= $grubStore->search . "\n";
+    }
+    $conf .= "  $extraPerEntryConfig\n" if $extraPerEntryConfig;
+    $conf .= "  multiboot $xen $xenParams\n" if $xen;
+    $conf .= "  " . ($xen ? "module" : "linux") . " $kernel $kernelParams\n";
+    $conf .= "  " . ($xen ? "module" : "initrd") . " $initrd\n";
+    $conf .= "}\n\n";
 }
 
 
@@ -556,7 +530,7 @@ sub addProfile {
     my ($profile, $description) = @_;
 
     # Add entries for all generations of this profile.
-    $conf .= "submenu \"$description\" --class submenu {\n" if $grubVersion == 2;
+    $conf .= "submenu \"$description\" --class submenu {\n";
 
     sub nrFromGen { my ($x) = @_; $x =~ /\/\w+-(\d+)-link/; return $1; }
 
@@ -579,17 +553,15 @@ sub addProfile {
         addEntry("@distroName@ - Configuration " . nrFromGen($link) . " ($date - $version)", $link, $subEntryOptions, 0);
     }
 
-    $conf .= "}\n" if $grubVersion == 2;
+    $conf .= "}\n";
 }
 
 addProfile "/nix/var/nix/profiles/system", "@distroName@ - All configurations";
 
-if ($grubVersion == 2) {
-    for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") {
-        my $name = basename($profile);
-        next unless $name =~ /^\w+$/;
-        addProfile $profile, "@distroName@ - Profile '$name'";
-    }
+for my $profile (glob "/nix/var/nix/profiles/system-profiles/*") {
+    my $name = basename($profile);
+    next unless $name =~ /^\w+$/;
+    addProfile $profile, "@distroName@ - Profile '$name'";
 }
 
 # extraPrepareConfig could refer to @bootPath@, which we have to substitute
@@ -601,16 +573,14 @@ if ($extraPrepareConfig ne "") {
 }
 
 # write the GRUB config.
-my $confFile = $grubVersion == 1 ? "$bootPath/grub/menu.lst" : "$bootPath/grub/grub.cfg";
+my $confFile = "$bootPath/grub/grub.cfg";
 my $tmpFile = $confFile . ".tmp";
 writeFile($tmpFile, $conf);
 
 
 # check whether to install GRUB EFI or not
 sub getEfiTarget {
-    if ($grubVersion == 1) {
-        return "no"
-    } elsif (($grub ne "") && ($grubEfi ne "")) {
+    if (($grub ne "") && ($grubEfi ne "")) {
         # EFI can only be installed when target is set;
         # A target is also required then for non-EFI grub
         if (($grubTarget eq "") || ($grubTargetEfi eq "")) { die }
@@ -735,7 +705,7 @@ symlink "$bootPath", "$tmpDir/boot" or die "Failed to symlink $tmpDir/boot: $!";
 if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
     foreach my $dev (@deviceTargets) {
         next if $dev eq "nodev";
-        print STDERR "installing the GRUB $grubVersion boot loader on $dev...\n";
+        print STDERR "installing the GRUB 2 boot loader on $dev...\n";
         my @command = ("$grub/sbin/grub-install", "--recheck", "--root-directory=$tmpDir", Cwd::abs_path($dev), @extraGrubInstallArgs);
         if ($forceInstall eq "true") {
             push @command, "--force";
@@ -750,7 +720,7 @@ if (($requireNewInstall != 0) && ($efiTarget eq "no" || $efiTarget eq "both")) {
 
 # install EFI GRUB
 if (($requireNewInstall != 0) && ($efiTarget eq "only" || $efiTarget eq "both")) {
-    print STDERR "installing the GRUB $grubVersion EFI boot loader into $efiSysMountPoint...\n";
+    print STDERR "installing the GRUB 2 boot loader into $efiSysMountPoint...\n";
     my @command = ("$grubEfi/sbin/grub-install", "--recheck", "--target=$grubTargetEfi", "--boot-directory=$bootPath", "--efi-directory=$efiSysMountPoint", @extraGrubInstallArgs);
     if ($forceInstall eq "true") {
         push @command, "--force";
diff --git a/nixos/modules/system/boot/loader/grub/ipxe.nix b/nixos/modules/system/boot/loader/grub/ipxe.nix
index adddcbee0164d..d926b7ceaa6e6 100644
--- a/nixos/modules/system/boot/loader/grub/ipxe.nix
+++ b/nixos/modules/system/boot/loader/grub/ipxe.nix
@@ -46,11 +46,7 @@ in
 
   config = mkIf (builtins.length scripts != 0) {
 
-    boot.loader.grub.extraEntries =
-      if config.boot.loader.grub.version == 2 then
-        toString (map grubEntry scripts)
-      else
-        throw "iPXE is not supported with GRUB 1.";
+    boot.loader.grub.extraEntries = toString (map grubEntry scripts);
 
     boot.loader.grub.extraFiles =
       { "ipxe.lkrn" = "${pkgs.ipxe}/ipxe.lkrn"; }
diff --git a/nixos/modules/system/boot/loader/grub/memtest.nix b/nixos/modules/system/boot/loader/grub/memtest.nix
index ccb6e8cc3caf5..ee969e9bff5bf 100644
--- a/nixos/modules/system/boot/loader/grub/memtest.nix
+++ b/nixos/modules/system/boot/loader/grub/memtest.nix
@@ -84,15 +84,11 @@ in
     })
 
     (mkIf (cfg.enable && !efiSupport) {
-      boot.loader.grub.extraEntries =
-        if config.boot.loader.grub.version == 2 then
-          ''
-            menuentry "Memtest86+" {
-              linux16 @bootRoot@/memtest.bin ${toString cfg.params}
-            }
-          ''
-        else
-          throw "Memtest86+ is not supported with GRUB 1.";
+      boot.loader.grub.extraEntries = ''
+        menuentry "Memtest86+" {
+          linux16 @bootRoot@/memtest.bin ${toString cfg.params}
+        }
+      '';
 
       boot.loader.grub.extraFiles."memtest.bin" = "${memtest86}/memtest.bin";
     })
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index 757dc080d4b61..8a3e89e5888bc 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -20,7 +20,7 @@ let
 
     nix = config.nix.package.out;
 
-    timeout = if config.boot.loader.timeout != null then config.boot.loader.timeout else "";
+    timeout = optionalString (config.boot.loader.timeout != null) config.boot.loader.timeout;
 
     editor = if cfg.editor then "True" else "False";
 
@@ -32,9 +32,9 @@ let
 
     inherit (config.system.nixos) distroName;
 
-    memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else "";
+    memtest86 = optionalString cfg.memtest86.enable pkgs.memtest86-efi;
 
-    netbootxyz = if cfg.netbootxyz.enable then pkgs.netbootxyz-efi else "";
+    netbootxyz = optionalString cfg.netbootxyz.enable pkgs.netbootxyz-efi;
 
     copyExtraFiles = pkgs.writeShellScript "copy-extra-files" ''
       empty_file=$(${pkgs.coreutils}/bin/mktemp)
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 8954c90812f92..b8f36538e70fe 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -1024,13 +1024,12 @@ in
         copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
 
         ${concatMapStringsSep "\n" (x:
-          if x.gpgCard != null then
+          optionalString (x.gpgCard != null)
             ''
               mkdir -p $out/secrets/gpg-keys/${x.device}
               cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
               cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
             ''
-          else ""
           ) (attrValues luks.devices)
         }
       ''}
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 05a667a09efc1..07f51f43184d9 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -6,8 +6,6 @@ with lib;
 
 let
 
-  cfg = config.systemd.network;
-
   check = {
 
     global = {
@@ -72,6 +70,9 @@ let
           "CombinedChannels"
           "RxBufferSize"
           "TxBufferSize"
+          "ReceiveQueues"
+          "TransmitQueues"
+          "TransmitQueueLength"
         ])
         (assertValueOneOf "MACAddressPolicy" ["persistent" "random" "none"])
         (assertMacAddress "MACAddress")
@@ -98,6 +99,9 @@ let
         (assertRange "CombinedChannels" 1 4294967295)
         (assertInt "RxBufferSize")
         (assertInt "TxBufferSize")
+        (assertRange "ReceiveQueues" 1 4096)
+        (assertRange "TransmitQueues" 1 4096)
+        (assertRange "TransmitQueueLength" 1 4294967294)
       ];
     };
 
@@ -2385,7 +2389,7 @@ let
     bridgeVLANConfig = mkOption {
       default = {};
       example = { VLAN = "10-20"; };
-      type = types.addCheck (types.attrsOf unitOption) check.network.sectionbridgeVLAN;
+      type = types.addCheck (types.attrsOf unitOption) check.network.sectionBridgeVLAN;
       description = lib.mdDoc ''
         Each attribute in this set specifies an option in the
         `[BridgeVLAN]` section of the unit.  See
@@ -2844,7 +2848,7 @@ let
         ''
         + optionalString (def.tokenBucketFilterConfig != { }) ''
           [TokenBucketFilter]
-          ${attrsToSection def.tockenBucketFilterConfig}
+          ${attrsToSection def.tokenBucketFilterConfig}
         ''
         + optionalString (def.pieConfig != { }) ''
           [PIE]
@@ -2941,16 +2945,14 @@ let
         + def.extraConfig;
     };
 
-  unitFiles = listToAttrs (map (name: {
-    name = "systemd/network/${name}";
+  mkUnitFiles = prefix: cfg: listToAttrs (map (name: {
+    name = "${prefix}systemd/network/${name}";
     value.source = "${cfg.units.${name}.unit}/${name}";
   }) (attrNames cfg.units));
-in
 
-{
-  options = {
+  commonOptions = visible: {
 
-    systemd.network.enable = mkOption {
+    enable = mkOption {
       default = false;
       type = types.bool;
       description = lib.mdDoc ''
@@ -2958,31 +2960,35 @@ in
       '';
     };
 
-    systemd.network.links = mkOption {
+    links = mkOption {
       default = {};
+      inherit visible;
       type = with types; attrsOf (submodule [ { options = linkOptions; } ]);
       description = lib.mdDoc "Definition of systemd network links.";
     };
 
-    systemd.network.netdevs = mkOption {
+    netdevs = mkOption {
       default = {};
+      inherit visible;
       type = with types; attrsOf (submodule [ { options = netdevOptions; } ]);
       description = lib.mdDoc "Definition of systemd network devices.";
     };
 
-    systemd.network.networks = mkOption {
+    networks = mkOption {
       default = {};
+      inherit visible;
       type = with types; attrsOf (submodule [ { options = networkOptions; } networkConfig ]);
       description = lib.mdDoc "Definition of systemd networks.";
     };
 
-    systemd.network.config = mkOption {
+    config = mkOption {
       default = {};
+      inherit visible;
       type = with types; submodule [ { options = networkdOptions; } networkdConfig ];
       description = lib.mdDoc "Definition of global systemd network config.";
     };
 
-    systemd.network.units = mkOption {
+    units = mkOption {
       description = lib.mdDoc "Definition of networkd units.";
       default = {};
       internal = true;
@@ -2995,7 +3001,7 @@ in
         }));
     };
 
-    systemd.network.wait-online = {
+    wait-online = {
       enable = mkOption {
         type = types.bool;
         default = true;
@@ -3051,12 +3057,11 @@ in
 
   };
 
-  config = mkMerge [
+  commonConfig = config: let cfg = config.systemd.network; in mkMerge [
 
     # .link units are honored by udev, no matter if systemd-networkd is enabled or not.
     {
       systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.link" (linkToUnit n v)) cfg.links;
-      environment.etc = unitFiles;
 
       systemd.network.wait-online.extraArgs =
         [ "--timeout=${toString cfg.wait-online.timeout}" ]
@@ -3066,14 +3071,6 @@ in
 
     (mkIf config.systemd.network.enable {
 
-      users.users.systemd-network.group = "systemd-network";
-
-      systemd.additionalUpstreamSystemUnits = [
-        "systemd-networkd-wait-online.service"
-        "systemd-networkd.service"
-        "systemd-networkd.socket"
-      ];
-
       systemd.network.units = mapAttrs' (n: v: nameValuePair "${n}.netdev" (netdevToUnit n v)) cfg.netdevs
         // mapAttrs' (n: v: nameValuePair "${n}.network" (networkToUnit n v)) cfg.networks;
 
@@ -3082,14 +3079,6 @@ in
       # networkd.
       systemd.sockets.systemd-networkd.wantedBy = [ "sockets.target" ];
 
-      systemd.services.systemd-networkd = {
-        wantedBy = [ "multi-user.target" ];
-        aliases = [ "dbus-org.freedesktop.network1.service" ];
-        restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [
-          config.environment.etc."systemd/networkd.conf".source
-        ];
-      };
-
       systemd.services.systemd-networkd-wait-online = {
         inherit (cfg.wait-online) enable;
         wantedBy = [ "network-online.target" ];
@@ -3111,8 +3100,37 @@ in
         };
       };
 
+    })
+  ];
+
+  stage2Config = let
+    cfg = config.systemd.network;
+    unitFiles = mkUnitFiles "" cfg;
+  in mkMerge [
+    (commonConfig config)
+
+    { environment.etc = unitFiles; }
+
+    (mkIf config.systemd.network.enable {
+
+      users.users.systemd-network.group = "systemd-network";
+
+      systemd.additionalUpstreamSystemUnits = [
+        "systemd-networkd-wait-online.service"
+        "systemd-networkd.service"
+        "systemd-networkd.socket"
+      ];
+
       environment.etc."systemd/networkd.conf" = renderConfig cfg.config;
 
+      systemd.services.systemd-networkd = {
+        wantedBy = [ "multi-user.target" ];
+        restartTriggers = map (x: x.source) (attrValues unitFiles) ++ [
+          config.environment.etc."systemd/networkd.conf".source
+        ];
+        aliases = [ "dbus-org.freedesktop.network1.service" ];
+      };
+
       networking.iproute2 = mkIf (cfg.config.addRouteTablesToIPRoute2 && cfg.config.routeTables != { }) {
         enable = mkDefault true;
         rttablesExtraConfig = ''
@@ -3123,6 +3141,117 @@ in
       };
 
       services.resolved.enable = mkDefault true;
+
+    })
+  ];
+
+  stage1Config = let
+    cfg = config.boot.initrd.systemd.network;
+  in mkMerge [
+    (commonConfig config.boot.initrd)
+
+    {
+      systemd.network.enable = mkDefault config.boot.initrd.network.enable;
+      systemd.contents = mkUnitFiles "/etc/" cfg;
+
+      # Networkd link files are used early by udev to set up interfaces early.
+      # This must be done in stage 1 to avoid race conditions between udev and
+      # network daemons.
+      systemd.network.units = lib.filterAttrs (n: _: hasSuffix ".link" n) config.systemd.network.units;
+      systemd.storePaths = ["${config.boot.initrd.systemd.package}/lib/systemd/network/99-default.link"];
+    }
+
+    (mkIf cfg.enable {
+
+      systemd.package = pkgs.systemdStage1Network;
+
+      # For networkctl
+      systemd.dbus.enable = mkDefault true;
+
+      systemd.additionalUpstreamUnits = [
+        "systemd-networkd-wait-online.service"
+        "systemd-networkd.service"
+        "systemd-networkd.socket"
+        "systemd-network-generator.service"
+        "network-online.target"
+        "network-pre.target"
+        "network.target"
+        "nss-lookup.target"
+        "nss-user-lookup.target"
+        "remote-fs-pre.target"
+        "remote-fs.target"
+      ];
+      systemd.users.systemd-network = {};
+      systemd.groups.systemd-network = {};
+
+      systemd.contents."/etc/systemd/networkd.conf" = renderConfig cfg.config;
+
+      systemd.services.systemd-networkd.wantedBy = [ "initrd.target" ];
+      systemd.services.systemd-network-generator.wantedBy = [ "sysinit.target" ];
+
+      systemd.storePaths = [
+        "${config.boot.initrd.systemd.package}/lib/systemd/systemd-networkd"
+        "${config.boot.initrd.systemd.package}/lib/systemd/systemd-networkd-wait-online"
+        "${config.boot.initrd.systemd.package}/lib/systemd/systemd-network-generator"
+      ];
+      kernelModules = [ "af_packet" ];
+
+      systemd.services.nixos-flush-networkd = mkIf config.boot.initrd.network.flushBeforeStage2 {
+        description = "Flush Network Configuration";
+        wantedBy = ["initrd.target"];
+        after = ["systemd-networkd.service" "dbus.socket" "dbus.service"];
+        before = ["shutdown.target" "initrd-switch-root.target"];
+        conflicts = ["shutdown.target" "initrd-switch-root.target"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig = {
+          # This service does nothing when starting, but brings down
+          # interfaces when switching root. This is the easiest way to
+          # ensure proper ordering while stopping. See systemd.unit(5)
+          # section on Before= and After=. The important part is that
+          # we are stopped before units we need, like dbus.service,
+          # and that we are stopped before starting units like
+          # initrd-switch-root.target
+          Type = "oneshot";
+          RemainAfterExit = true;
+          ExecStart = "/bin/true";
+        };
+        # systemd-networkd doesn't bring down interfaces on its own
+        # when it exits (see: systemd-networkd(8)), so we have to do
+        # it ourselves. The networkctl command doesn't have a way to
+        # bring all interfaces down, so we have to iterate over the
+        # list and filter out unmanaged interfaces to bring them down
+        # individually.
+        preStop = ''
+          networkctl list --full --no-legend | while read _idx link _type _operational setup _; do
+            [ "$setup" = unmanaged ] && continue
+            networkctl down "$link"
+          done
+        '';
+      };
+
+    })
+  ];
+
+in
+
+{
+  options = {
+    systemd.network = commonOptions true;
+    boot.initrd.systemd.network = commonOptions "shallow";
+  };
+
+  config = mkMerge [
+    stage2Config
+    (mkIf config.boot.initrd.systemd.enable {
+      assertions = [{
+        assertion = config.boot.initrd.network.udhcpc.extraArgs == [];
+        message = ''
+          boot.initrd.network.udhcpc.extraArgs is not supported when
+          boot.initrd.systemd.enable is enabled
+        '';
+      }];
+
+      boot.initrd = stage1Config;
     })
   ];
 }
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index af57310bda7d9..387c27d86ebbe 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -293,6 +293,9 @@ checkFS() {
     # Skip fsck for inherently readonly filesystems.
     if [ "$fsType" = squashfs ]; then return 0; fi
 
+    # Skip fsck.erofs because it is still experimental.
+    if [ "$fsType" = erofs ]; then return 0; fi
+
     # If we couldn't figure out the FS type, then skip fsck.
     if [ "$fsType" = auto ]; then
         echo 'cannot check filesystem with type "auto"!'
@@ -410,6 +413,11 @@ mountFS() {
         n=$((n + 1))
     done
 
+    # For bind mounts, busybox has a tendency to ignore options, which can be a
+    # security issue (e.g. "nosuid"). Remounting the partition seems to fix the
+    # issue.
+    mount "/mnt-root$mountPoint" -o "remount,$optionsPrefixed"
+
     [ "$mountPoint" == "/" ] &&
         [ -f "/mnt-root/etc/NIXOS_LUSTRATE" ] &&
         lustrateRoot "/mnt-root"
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index d26ea7597c450..1229f6357523f 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -445,7 +445,8 @@ let
           ) config.boot.initrd.secrets)
          }
 
-        (cd "$tmp" && find . -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
+        # mindepth 1 so that we don't change the mode of /
+        (cd "$tmp" && find . -mindepth 1 -print0 | sort -z | bsdtar --uid 0 --gid 0 -cnf - -T - | bsdtar --null -cf - --format=newc @-) | \
           ${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
       '';
 
diff --git a/nixos/modules/system/boot/systemd/initrd-secrets.nix b/nixos/modules/system/boot/systemd/initrd-secrets.nix
index bc65880719d7a..7b59c0cbe7b84 100644
--- a/nixos/modules/system/boot/systemd/initrd-secrets.nix
+++ b/nixos/modules/system/boot/systemd/initrd-secrets.nix
@@ -19,13 +19,13 @@
       # drop this service, we'd mount the /run tmpfs over the secret, making it
       # invisible in stage 2.
       script = ''
-        for secret in $(cd /.initrd-secrets; find . -type f); do
+        for secret in $(cd /.initrd-secrets; find . -type f -o -type l); do
           mkdir -p "$(dirname "/$secret")"
           cp "/.initrd-secrets/$secret" "/$secret"
         done
       '';
 
-      unitConfig = {
+      serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = true;
       };
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index ffe96f3ad9c30..c9c219d0a0a59 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -1,4 +1,4 @@
-{ lib, config, utils, pkgs, ... }:
+{ lib, options, config, utils, pkgs, ... }:
 
 with lib;
 
@@ -72,15 +72,6 @@ let
     "systemd-tmpfiles-setup.service"
     "timers.target"
     "umount.target"
-
-    # TODO: Networking
-    # "network-online.target"
-    # "network-pre.target"
-    # "network.target"
-    # "nss-lookup.target"
-    # "nss-user-lookup.target"
-    # "remote-fs-pre.target"
-    # "remote-fs.target"
   ] ++ cfg.additionalUpstreamUnits;
 
   upstreamWants = [
@@ -135,18 +126,20 @@ in {
   options.boot.initrd.systemd = {
     enable = mkEnableOption (lib.mdDoc "systemd in initrd") // {
       description = lib.mdDoc ''
-        Whether to enable systemd in initrd.
-
-        Note: This is in very early development and is highly
-        experimental. Most of the features NixOS supports in initrd are
-        not yet supported by the intrd generated with this option.
+        Whether to enable systemd in initrd. The unit options such as
+        {option}`boot.initrd.systemd.services` are the same as their
+        stage 2 counterparts such as {option}`systemd.services`,
+        except that `restartTriggers` and `reloadTriggers` are not
+        supported.
+
+        Note: This is experimental. Some of the `boot.initrd` options
+        are not supported when this is enabled, and the options under
+        `boot.initrd.systemd` are subject to change.
       '';
     };
 
-    package = (mkPackageOptionMD pkgs "systemd" {
+    package = mkPackageOptionMD pkgs "systemd" {
       default = "systemdStage1";
-    }) // {
-      visible = false;
     };
 
     extraConfig = mkOption {
@@ -176,7 +169,6 @@ in {
           "/etc/hostname".text = "mymachine";
         }
       '';
-      visible = false;
       default = {};
       type = utils.systemdUtils.types.initrdContents;
     };
@@ -226,7 +218,6 @@ in {
 
     emergencyAccess = mkOption {
       type = with types; oneOf [ bool (nullOr (passwdEntry str)) ];
-      visible = false;
       description = lib.mdDoc ''
         Set to true for unauthenticated emergency access, and false for
         no emergency access.
@@ -240,7 +231,6 @@ in {
     initrdBin = mkOption {
       type = types.listOf types.package;
       default = [];
-      visible = false;
       description = lib.mdDoc ''
         Packages to include in /bin for the stage 1 emergency shell.
       '';
@@ -249,7 +239,6 @@ in {
     additionalUpstreamUnits = mkOption {
       default = [ ];
       type = types.listOf types.str;
-      visible = false;
       example = [ "debug-shell.service" "systemd-quotacheck.service" ];
       description = lib.mdDoc ''
         Additional units shipped with systemd that shall be enabled.
@@ -260,7 +249,6 @@ in {
       default = [ ];
       type = types.listOf types.str;
       example = [ "systemd-backlight@.service" ];
-      visible = false;
       description = lib.mdDoc ''
         A list of units to skip when generating system systemd configuration directory. This has
         priority over upstream units, {option}`boot.initrd.systemd.units`, and
@@ -273,13 +261,12 @@ in {
     units = mkOption {
       description = lib.mdDoc "Definition of systemd units.";
       default = {};
-      visible = false;
+      visible = "shallow";
       type = systemdUtils.types.units;
     };
 
     packages = mkOption {
       default = [];
-      visible = false;
       type = types.listOf types.package;
       example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
       description = lib.mdDoc "Packages providing systemd units and hooks.";
@@ -287,7 +274,7 @@ in {
 
     targets = mkOption {
       default = {};
-      visible = false;
+      visible = "shallow";
       type = systemdUtils.types.initrdTargets;
       description = lib.mdDoc "Definition of systemd target units.";
     };
@@ -295,35 +282,35 @@ in {
     services = mkOption {
       default = {};
       type = systemdUtils.types.initrdServices;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of systemd service units.";
     };
 
     sockets = mkOption {
       default = {};
       type = systemdUtils.types.initrdSockets;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of systemd socket units.";
     };
 
     timers = mkOption {
       default = {};
       type = systemdUtils.types.initrdTimers;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of systemd timer units.";
     };
 
     paths = mkOption {
       default = {};
       type = systemdUtils.types.initrdPaths;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of systemd path units.";
     };
 
     mounts = mkOption {
       default = [];
       type = systemdUtils.types.initrdMounts;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc ''
         Definition of systemd mount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -334,7 +321,7 @@ in {
     automounts = mkOption {
       default = [];
       type = systemdUtils.types.automounts;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc ''
         Definition of systemd automount units.
         This is a list instead of an attrSet, because systemd mandates the names to be derived from
@@ -345,7 +332,7 @@ in {
     slices = mkOption {
       default = {};
       type = systemdUtils.types.slices;
-      visible = false;
+      visible = "shallow";
       description = lib.mdDoc "Definition of slice configurations.";
     };
   };
@@ -378,7 +365,7 @@ in {
 
         "/etc/systemd/system.conf".text = ''
           [Manager]
-          DefaultEnvironment=PATH=/bin:/sbin ${optionalString (isBool cfg.emergencyAccess && cfg.emergencyAccess) "SYSTEMD_SULOGIN_FORCE=1"}
+          DefaultEnvironment=PATH=/bin:/sbin
           ${cfg.extraConfig}
           ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
         '';
@@ -388,8 +375,10 @@ in {
 
         "/etc/modules-load.d/nixos.conf".text = concatStringsSep "\n" config.boot.initrd.kernelModules;
 
-        "/etc/passwd".source = "${pkgs.fakeNss}/etc/passwd";
-        "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then "!" else cfg.emergencyAccess}:::::::";
+        # We can use either ! or * to lock the root account in the
+        # console, but some software like OpenSSH won't even allow you
+        # to log in with an SSH key if you use ! so we use * instead
+        "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then optionalString (!cfg.emergencyAccess) "*" else cfg.emergencyAccess}:::::::";
 
         "/bin".source = "${initrdBinEnv}/bin";
         "/sbin".source = "${initrdBinEnv}/sbin";
diff --git a/nixos/modules/system/boot/systemd/repart.nix b/nixos/modules/system/boot/systemd/repart.nix
index 8f3a700237700..251c7e2361231 100644
--- a/nixos/modules/system/boot/systemd/repart.nix
+++ b/nixos/modules/system/boot/systemd/repart.nix
@@ -72,11 +72,6 @@ in
   };
 
   config = lib.mkIf (cfg.enable || initrdCfg.enable) {
-    # Always link the definitions into /etc so that they are also included in
-    # the /nix/store of the sysroot during early userspace (i.e. while in the
-    # initrd).
-    environment.etc."repart.d".source = definitionsDirectory;
-
     boot.initrd.systemd = lib.mkIf initrdCfg.enable {
       additionalUpstreamUnits = [
         "systemd-repart.service"
@@ -86,33 +81,40 @@ in
         "${config.boot.initrd.systemd.package}/bin/systemd-repart"
       ];
 
+      contents."/etc/repart.d".source = definitionsDirectory;
+
       # Override defaults in upstream unit.
       services.systemd-repart = {
-        # Unset the conditions as they cannot be met before activation because
-        # the definition files are not stored in the expected locations.
-        unitConfig.ConditionDirectoryNotEmpty = [
-          " " # required to unset the previous value.
-        ];
+        # systemd-repart tries to create directories in /var/tmp by default to
+        # store large temporary files that benefit from persistence on disk. In
+        # the initrd, however, /var/tmp does not provide more persistence than
+        # /tmp, so we re-use it here.
+        environment."TMPDIR" = "/tmp";
         serviceConfig = {
-          # systemd-repart runs before the activation script. Thus we cannot
-          # rely on them being linked in /etc already. Instead we have to
-          # explicitly pass their location in the sysroot to the binary.
           ExecStart = [
             " " # required to unset the previous value.
+            # When running in the initrd, systemd-repart by default searches
+            # for definition files in /sysroot or /sysusr. We tell it to look
+            # in the initrd itself.
             ''${config.boot.initrd.systemd.package}/bin/systemd-repart \
-                  --definitions=/sysroot${definitionsDirectory} \
+                  --definitions=/etc/repart.d \
                   --dry-run=no
             ''
           ];
         };
-        # Because the initrd does not have the `initrd-usr-fs.target` the
-        # upestream unit runs too early in the boot process, before the sysroot
-        # is available. However, systemd-repart needs access to the sysroot to
-        # find the definition files.
+        # systemd-repart needs to run after /sysroot (or /sysuser, but we don't
+        # have it) has been mounted because otherwise it cannot determine the
+        # device (i.e disk) to operate on. If you want to run systemd-repart
+        # without /sysroot, you have to explicitly tell it which device to
+        # operate on.
         after = [ "sysroot.mount" ];
       };
     };
 
+    environment.etc = lib.mkIf cfg.enable {
+      "repart.d".source = definitionsDirectory;
+    };
+
     systemd = lib.mkIf cfg.enable {
       additionalUpstreamSystemUnits = [
         "systemd-repart.service"
@@ -120,4 +122,5 @@ in
     };
   };
 
+  meta.maintainers = with lib.maintainers; [ nikstur ];
 }
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 822f1593474eb..326862f836a5c 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -319,7 +319,7 @@ in
         message = let
           fs = head (filter notAutoResizable fileSystems);
         in
-          "Mountpoint '${fs.mountPoint}': 'autoResize = true' is not supported for 'fsType = \"${fs.fsType}\"':${if fs.fsType == "auto" then " fsType has to be explicitly set and" else ""} only the ext filesystems and f2fs support it.";
+          "Mountpoint '${fs.mountPoint}': 'autoResize = true' is not supported for 'fsType = \"${fs.fsType}\"':${optionalString (fs.fsType == "auto") " fsType has to be explicitly set and"} only the ext filesystems and f2fs support it.";
       }
     ];
 
diff --git a/nixos/modules/tasks/filesystems/envfs.nix b/nixos/modules/tasks/filesystems/envfs.nix
index 76344f5f87eaf..365cb46ff2fe3 100644
--- a/nixos/modules/tasks/filesystems/envfs.nix
+++ b/nixos/modules/tasks/filesystems/envfs.nix
@@ -12,12 +12,13 @@ let
           ln -s ${config.environment.usrbinenv} $out/env
           ln -s ${config.environment.binsh} $out/sh
         '' + cfg.extraFallbackPathCommands)}"
+        "nofail"
       ];
     };
     "/bin" = {
       device = "/usr/bin";
       fsType = "none";
-      options = [ "bind" ];
+      options = [ "bind" "nofail" ];
     };
   };
 in {
diff --git a/nixos/modules/tasks/filesystems/erofs.nix b/nixos/modules/tasks/filesystems/erofs.nix
new file mode 100644
index 0000000000000..a3d6576693505
--- /dev/null
+++ b/nixos/modules/tasks/filesystems/erofs.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inInitrd = lib.any (fs: fs == "erofs") config.boot.initrd.supportedFilesystems;
+  inSystem = lib.any (fs: fs == "erofs") config.boot.supportedFilesystems;
+
+in
+
+{
+  config = lib.mkIf (inInitrd || inSystem) {
+
+    system.fsPackages = [ pkgs.erofs-utils ];
+
+    boot.initrd.availableKernelModules = lib.mkIf inInitrd [ "erofs" ];
+
+    # fsck.erofs is currently experimental and should not be run as a
+    # privileged user. Thus, it is not included in the initrd.
+
+  };
+}
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index b24b29c32d4ac..0fcd3c10219c1 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -28,11 +28,164 @@ let
     # TODO: warn the user that any address configured on those interfaces will be useless
     ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches);
 
+  domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
+  genericNetwork = override:
+    let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
+      ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
+        makeGateway = gateway: {
+          routeConfig = {
+            Gateway = gateway;
+            GatewayOnLink = false;
+          };
+        };
+    in optionalAttrs (gateway != [ ]) {
+      routes = override (map makeGateway gateway);
+    } // optionalAttrs (domains != [ ]) {
+      domains = override domains;
+    };
+
+  genericDhcpNetworks = initrd: mkIf cfg.useDHCP {
+    networks."99-ethernet-default-dhcp" = {
+      # We want to match physical ethernet interfaces as commonly
+      # found on laptops, desktops and servers, to provide an
+      # "out-of-the-box" setup that works for common cases.  This
+      # heuristic isn't perfect (it could match interfaces with
+      # custom names that _happen_ to start with en or eth), but
+      # should be good enough to make the common case easy and can
+      # be overridden on a case-by-case basis using
+      # higher-priority networks or by disabling useDHCP.
+
+      # Type=ether matches veth interfaces as well, and this is
+      # more likely to result in interfaces being configured to
+      # use DHCP when they shouldn't.
+
+      # When wait-online.anyInterface is enabled, RequiredForOnline really
+      # means "sufficient for online", so we can enable it.
+      # Otherwise, don't block the network coming online because of default networks.
+      matchConfig.Name = ["en*" "eth*"];
+      DHCP = "yes";
+      linkConfig.RequiredForOnline =
+        lib.mkDefault (if initrd
+        then config.boot.initrd.systemd.network.wait-online.anyInterface
+        else config.systemd.network.wait-online.anyInterface);
+      networkConfig.IPv6PrivacyExtensions = "kernel";
+    };
+    networks."99-wireless-client-dhcp" = {
+      # Like above, but this is much more likely to be correct.
+      matchConfig.WLANInterfaceType = "station";
+      DHCP = "yes";
+      linkConfig.RequiredForOnline =
+        lib.mkDefault config.systemd.network.wait-online.anyInterface;
+      networkConfig.IPv6PrivacyExtensions = "kernel";
+      # We also set the route metric to one more than the default
+      # of 1024, so that Ethernet is preferred if both are
+      # available.
+      dhcpV4Config.RouteMetric = 1025;
+      ipv6AcceptRAConfig.RouteMetric = 1025;
+    };
+  };
+
+
+  interfaceNetworks = mkMerge (forEach interfaces (i: {
+    netdevs = mkIf i.virtual ({
+      "40-${i.name}" = {
+        netdevConfig = {
+          Name = i.name;
+          Kind = i.virtualType;
+        };
+        "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
+          User = i.virtualOwner;
+        };
+      };
+    });
+    networks."40-${i.name}" = mkMerge [ (genericNetwork id) {
+      name = mkDefault i.name;
+      DHCP = mkForce (dhcpStr
+        (if i.useDHCP != null then i.useDHCP else false));
+      address = forEach (interfaceIps i)
+        (ip: "${ip.address}/${toString ip.prefixLength}");
+      routes = forEach (interfaceRoutes i)
+        (route: {
+          # Most of these route options have not been tested.
+          # Please fix or report any mistakes you may find.
+          routeConfig =
+            optionalAttrs (route.address != null && route.prefixLength != null) {
+              Destination = "${route.address}/${toString route.prefixLength}";
+            } //
+            optionalAttrs (route.options ? fastopen_no_cookie) {
+              FastOpenNoCookie = route.options.fastopen_no_cookie;
+            } //
+            optionalAttrs (route.via != null) {
+              Gateway = route.via;
+            } //
+            optionalAttrs (route.type != null) {
+              Type = route.type;
+            } //
+            optionalAttrs (route.options ? onlink) {
+              GatewayOnLink = true;
+            } //
+            optionalAttrs (route.options ? initrwnd) {
+              InitialAdvertisedReceiveWindow = route.options.initrwnd;
+            } //
+            optionalAttrs (route.options ? initcwnd) {
+              InitialCongestionWindow = route.options.initcwnd;
+            } //
+            optionalAttrs (route.options ? pref) {
+              IPv6Preference = route.options.pref;
+            } //
+            optionalAttrs (route.options ? mtu) {
+              MTUBytes = route.options.mtu;
+            } //
+            optionalAttrs (route.options ? metric) {
+              Metric = route.options.metric;
+            } //
+            optionalAttrs (route.options ? src) {
+              PreferredSource = route.options.src;
+            } //
+            optionalAttrs (route.options ? protocol) {
+              Protocol = route.options.protocol;
+            } //
+            optionalAttrs (route.options ? quickack) {
+              QuickAck = route.options.quickack;
+            } //
+            optionalAttrs (route.options ? scope) {
+              Scope = route.options.scope;
+            } //
+            optionalAttrs (route.options ? from) {
+              Source = route.options.from;
+            } //
+            optionalAttrs (route.options ? table) {
+              Table = route.options.table;
+            } //
+            optionalAttrs (route.options ? advmss) {
+              TCPAdvertisedMaximumSegmentSize = route.options.advmss;
+            } //
+            optionalAttrs (route.options ? ttl-propagate) {
+              TTLPropagate = route.options.ttl-propagate == "enabled";
+            };
+        });
+      networkConfig.IPv6PrivacyExtensions = "kernel";
+      linkConfig = optionalAttrs (i.macAddress != null) {
+        MACAddress = i.macAddress;
+      } // optionalAttrs (i.mtu != null) {
+        MTUBytes = toString i.mtu;
+      };
+    }];
+  }));
+
 in
 
 {
+  config = mkMerge [
 
-  config = mkIf cfg.useNetworkd {
+  (mkIf config.boot.initrd.network.enable {
+    # Note this is if initrd.network.enable, not if
+    # initrd.systemd.network.enable. By setting the latter and not the
+    # former, the user retains full control over the configuration.
+    boot.initrd.systemd.network = mkMerge [(genericDhcpNetworks true) interfaceNetworks];
+  })
+
+  (mkIf cfg.useNetworkd {
 
     assertions = [ {
       assertion = cfg.defaultGatewayWindowSize == null;
@@ -54,149 +207,11 @@ in
     networking.dhcpcd.enable = mkDefault false;
 
     systemd.network =
-      let
-        domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
-        genericNetwork = override:
-          let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
-            ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
-              makeGateway = gateway: {
-                routeConfig = {
-                  Gateway = gateway;
-                  GatewayOnLink = false;
-                };
-              };
-          in optionalAttrs (gateway != [ ]) {
-            routes = override (map makeGateway gateway);
-          } // optionalAttrs (domains != [ ]) {
-            domains = override domains;
-          };
-      in mkMerge [ {
+      mkMerge [ {
         enable = true;
       }
-      (mkIf cfg.useDHCP {
-        networks."99-ethernet-default-dhcp" = lib.mkIf cfg.useDHCP {
-          # We want to match physical ethernet interfaces as commonly
-          # found on laptops, desktops and servers, to provide an
-          # "out-of-the-box" setup that works for common cases.  This
-          # heuristic isn't perfect (it could match interfaces with
-          # custom names that _happen_ to start with en or eth), but
-          # should be good enough to make the common case easy and can
-          # be overridden on a case-by-case basis using
-          # higher-priority networks or by disabling useDHCP.
-
-          # Type=ether matches veth interfaces as well, and this is
-          # more likely to result in interfaces being configured to
-          # use DHCP when they shouldn't.
-
-          # When wait-online.anyInterface is enabled, RequiredForOnline really
-          # means "sufficient for online", so we can enable it.
-          # Otherwise, don't block the network coming online because of default networks.
-          matchConfig.Name = ["en*" "eth*"];
-          DHCP = "yes";
-          linkConfig.RequiredForOnline =
-            lib.mkDefault config.systemd.network.wait-online.anyInterface;
-          networkConfig.IPv6PrivacyExtensions = "kernel";
-        };
-        networks."99-wireless-client-dhcp" = lib.mkIf cfg.useDHCP {
-          # Like above, but this is much more likely to be correct.
-          matchConfig.WLANInterfaceType = "station";
-          DHCP = "yes";
-          linkConfig.RequiredForOnline =
-            lib.mkDefault config.systemd.network.wait-online.anyInterface;
-          networkConfig.IPv6PrivacyExtensions = "kernel";
-          # We also set the route metric to one more than the default
-          # of 1024, so that Ethernet is preferred if both are
-          # available.
-          dhcpV4Config.RouteMetric = 1025;
-          ipv6AcceptRAConfig.RouteMetric = 1025;
-        };
-      })
-      (mkMerge (forEach interfaces (i: {
-        netdevs = mkIf i.virtual ({
-          "40-${i.name}" = {
-            netdevConfig = {
-              Name = i.name;
-              Kind = i.virtualType;
-            };
-            "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
-              User = i.virtualOwner;
-            };
-          };
-        });
-        networks."40-${i.name}" = mkMerge [ (genericNetwork id) {
-          name = mkDefault i.name;
-          DHCP = mkForce (dhcpStr
-            (if i.useDHCP != null then i.useDHCP else false));
-          address = forEach (interfaceIps i)
-            (ip: "${ip.address}/${toString ip.prefixLength}");
-          routes = forEach (interfaceRoutes i)
-            (route: {
-              # Most of these route options have not been tested.
-              # Please fix or report any mistakes you may find.
-              routeConfig =
-                optionalAttrs (route.address != null && route.prefixLength != null) {
-                  Destination = "${route.address}/${toString route.prefixLength}";
-                } //
-                optionalAttrs (route.options ? fastopen_no_cookie) {
-                  FastOpenNoCookie = route.options.fastopen_no_cookie;
-                } //
-                optionalAttrs (route.via != null) {
-                  Gateway = route.via;
-                } //
-                optionalAttrs (route.type != null) {
-                  Type = route.type;
-                } //
-                optionalAttrs (route.options ? onlink) {
-                  GatewayOnLink = true;
-                } //
-                optionalAttrs (route.options ? initrwnd) {
-                  InitialAdvertisedReceiveWindow = route.options.initrwnd;
-                } //
-                optionalAttrs (route.options ? initcwnd) {
-                  InitialCongestionWindow = route.options.initcwnd;
-                } //
-                optionalAttrs (route.options ? pref) {
-                  IPv6Preference = route.options.pref;
-                } //
-                optionalAttrs (route.options ? mtu) {
-                  MTUBytes = route.options.mtu;
-                } //
-                optionalAttrs (route.options ? metric) {
-                  Metric = route.options.metric;
-                } //
-                optionalAttrs (route.options ? src) {
-                  PreferredSource = route.options.src;
-                } //
-                optionalAttrs (route.options ? protocol) {
-                  Protocol = route.options.protocol;
-                } //
-                optionalAttrs (route.options ? quickack) {
-                  QuickAck = route.options.quickack;
-                } //
-                optionalAttrs (route.options ? scope) {
-                  Scope = route.options.scope;
-                } //
-                optionalAttrs (route.options ? from) {
-                  Source = route.options.from;
-                } //
-                optionalAttrs (route.options ? table) {
-                  Table = route.options.table;
-                } //
-                optionalAttrs (route.options ? advmss) {
-                  TCPAdvertisedMaximumSegmentSize = route.options.advmss;
-                } //
-                optionalAttrs (route.options ? ttl-propagate) {
-                  TTLPropagate = route.options.ttl-propagate == "enabled";
-                };
-            });
-          networkConfig.IPv6PrivacyExtensions = "kernel";
-          linkConfig = optionalAttrs (i.macAddress != null) {
-            MACAddress = i.macAddress;
-          } // optionalAttrs (i.mtu != null) {
-            MTUBytes = toString i.mtu;
-          };
-        }];
-      })))
+      (genericDhcpNetworks false)
+      interfaceNetworks
       (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
         netdevs."40-${name}" = {
           netdevConfig = {
@@ -437,6 +452,7 @@ in
               bindsTo = [ "systemd-networkd.service" ];
           };
       };
-  };
+  })
 
+  ];
 }
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index 028099c64643c..9c4bbecf48090 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -36,8 +36,16 @@ in
             while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done
             echo "connecting to host..." >&2
             stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion
-            echo
-            PS1= exec /bin/sh
+            # The following line is essential since it signals to
+            # the test driver that the shell is ready.
+            # See: the connect method in the Machine class.
+            echo "Spawning backdoor root shell..."
+            # Passing the terminal device makes bash run non-interactively.
+            # Otherwise we get errors on the terminal because bash tries to
+            # setup things like job control.
+            # Note: calling bash explicitely 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";
       };
diff --git a/nixos/modules/virtualisation/azure-common.nix b/nixos/modules/virtualisation/azure-common.nix
index f29d368137ae0..cd1ffdb6cbcc3 100644
--- a/nixos/modules/virtualisation/azure-common.nix
+++ b/nixos/modules/virtualisation/azure-common.nix
@@ -12,7 +12,6 @@ with lib;
 
   # Generate a GRUB menu.
   boot.loader.grub.device = "/dev/sda";
-  boot.loader.grub.version = 2;
   boot.loader.timeout = 0;
 
   boot.growPartition = true;
diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix
index 96b749102241d..55b285b69147a 100644
--- a/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixos/modules/virtualisation/lxc-container.nix
@@ -161,6 +161,11 @@ in
       extraCommands = "mkdir -p proc sys dev";
     };
 
+    system.build.installBootLoader = pkgs.writeScript "install-lxd-sbin-init.sh" ''
+      #!${pkgs.runtimeShell}
+      ln -fs "$1/init" /sbin/init
+    '';
+
     # Add the overrides from lxd distrobuilder
     # https://github.com/lxc/distrobuilder/blob/05978d0d5a72718154f1525c7d043e090ba7c3e0/distrobuilder/main.go#L630
     systemd.packages = [
diff --git a/nixos/modules/virtualisation/multipass.nix b/nixos/modules/virtualisation/multipass.nix
index 6ef7de4b2bf50..b331b3be7ea58 100644
--- a/nixos/modules/virtualisation/multipass.nix
+++ b/nixos/modules/virtualisation/multipass.nix
@@ -33,8 +33,8 @@ in
       description = "Multipass orchestrates virtual Ubuntu instances.";
 
       wantedBy = [ "multi-user.target" ];
-      wants = [ "network.target" ];
-      after = [ "network.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
 
       environment = {
         "XDG_DATA_HOME" = "/var/lib/multipass/data";
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 669981da59740..c3949564d4bde 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -170,11 +170,11 @@ let
         --setenv HOST_PORT="$HOST_PORT" \
         --setenv PATH="$PATH" \
         ${optionalString cfg.ephemeral "--ephemeral"} \
-        ${if cfg.additionalCapabilities != null && cfg.additionalCapabilities != [] then
-          ''--capability="${concatStringsSep "," cfg.additionalCapabilities}"'' else ""
+        ${optionalString (cfg.additionalCapabilities != null && cfg.additionalCapabilities != [])
+          ''--capability="${concatStringsSep "," cfg.additionalCapabilities}"''
         } \
-        ${if cfg.tmpfs != null && cfg.tmpfs != [] then
-          ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}'' else ""
+        ${optionalString (cfg.tmpfs != null && cfg.tmpfs != [])
+          ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}''
         } \
         ${containerInit cfg} "''${SYSTEM_PATH:-/nix/var/nix/profiles/system}/init"
     '';
@@ -515,6 +515,10 @@ in
                     in [ extraConfig ] ++ (map (x: x.value) defs);
                   prefix = [ "containers" name ];
                   inherit (config) specialArgs;
+
+                  # The system is inherited from the host above.
+                  # Set it to null, to remove the "legacy" entrypoint's non-hermetic default.
+                  system = null;
                 }).config;
               };
             };
diff --git a/nixos/modules/virtualisation/parallels-guest.nix b/nixos/modules/virtualisation/parallels-guest.nix
index 07a61bf208db3..dba8ce02b724c 100644
--- a/nixos/modules/virtualisation/parallels-guest.nix
+++ b/nixos/modules/virtualisation/parallels-guest.nix
@@ -87,7 +87,6 @@ in
       bindsTo = [ "cups.service" ];
       path = [ prl-tools ];
       serviceConfig = {
-        Type = "forking";
         ExecStart = "${prl-tools}/bin/prlshprint";
         WorkingDirectory = "${prl-tools}/bin";
       };
diff --git a/nixos/modules/virtualisation/proxmox-image.nix b/nixos/modules/virtualisation/proxmox-image.nix
index c66a4f178ec73..82b33a341799e 100644
--- a/nixos/modules/virtualisation/proxmox-image.nix
+++ b/nixos/modules/virtualisation/proxmox-image.nix
@@ -193,6 +193,7 @@ with lib;
             sha256 = "sha256-9rN1x5UfcoQCeYsLqrsthkeMpT1Eztvvq74cRr9G+Dk=";
           };
           patches = [
+            # Proxmox' VMA tool is published as a particular patch upon QEMU
             (pkgs.fetchpatch {
               url =
                 let
@@ -201,6 +202,21 @@ with lib;
                 in "https://git.proxmox.com/?p=pve-qemu.git;a=blob_plain;hb=${rev};f=${path}";
               hash = "sha256-2Dz+ceTwrcyYYxi76RtyY3v15/2pwGcDhFuoZWlgbjc=";
             })
+
+            # Proxmox' VMA tool uses O_DIRECT which fails on tmpfs
+            # Filed to upstream issue tracker: https://bugzilla.proxmox.com/show_bug.cgi?id=4710
+            (pkgs.writeText "inline.patch" ''
+                --- a/vma-writer.c   2023-05-01 15:11:13.361341177 +0200
+                +++ b/vma-writer.c   2023-05-01 15:10:51.785293129 +0200
+                @@ -306,7 +306,7 @@
+                             /* try to use O_NONBLOCK */
+                             fcntl(vmaw->fd, F_SETFL, fcntl(vmaw->fd, F_GETFL)|O_NONBLOCK);
+                         } else  {
+                -            oflags = O_NONBLOCK|O_DIRECT|O_WRONLY|O_EXCL;
+                +            oflags = O_NONBLOCK|O_WRONLY|O_EXCL;
+                             vmaw->fd = qemu_create(filename, oflags, 0644, errp);
+                         }
+            '')
           ];
 
           buildInputs = super.buildInputs ++ [ pkgs.libuuid ];
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 89772019284cb..5f6bf4b39e974 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -55,6 +55,11 @@ let
 
   };
 
+  selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }:
+  if useDefaultFilesystems then
+    if useEFIBoot then "efi" else "legacy"
+  else "none";
+
   driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }:
     let
       drvId = "drive${toString idx}";
@@ -98,7 +103,6 @@ let
   addDeviceNames =
     imap1 (idx: drive: drive // { device = driveDeviceName idx; });
 
-
   # Shell script to start the VM.
   startVM =
     ''
@@ -111,8 +115,23 @@ let
       NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE"
 
       if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then
-          ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \
-            ${toString config.virtualisation.diskSize}M
+          echo "Disk image do not exist, creating the virtualisation disk image..."
+          # If we are using a bootloader and default filesystems layout.
+          # We have to reuse the system image layout as a backing image format (CoW)
+          # So we can write on the top of it.
+
+          # If we are not using the default FS layout, potentially, we are interested into
+          # performing operations in postDeviceCommands or at early boot on the raw device.
+          # We can still boot through QEMU direct kernel boot feature.
+
+          # CoW prevent size to be attributed to an image.
+          # FIXME: raise this issue to upstream.
+          ${qemu}/bin/qemu-img create \
+          ${concatStringsSep " \\\n" ([ "-f qcow2" ]
+          ++ optional (cfg.useBootLoader && cfg.useDefaultFilesystems) "-F qcow2 -b ${systemImage}/nixos.qcow2"
+          ++ optional (!(cfg.useBootLoader && cfg.useDefaultFilesystems)) "-o size=${toString config.virtualisation.diskSize}M"
+          ++ [ ''"$NIX_DISK_IMAGE"'' ])}
+          echo "Virtualisation disk image created."
       fi
 
       # Create a directory for storing temporary data of the running VM.
@@ -150,24 +169,26 @@ let
       # Create a directory for exchanging data with the VM.
       mkdir -p "$TMPDIR/xchg"
 
-      ${lib.optionalString cfg.useBootLoader
+      ${lib.optionalString cfg.useEFIBoot
       ''
-        if ${if !cfg.persistBootDevice then "true" else "! test -e $TMPDIR/disk.img"}; then
-          # Create a writable copy/snapshot of the boot disk.
-          # A writable boot disk can be booted from automatically.
-          ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img"
+        # Expose EFI variables, it's useful even when we are not using a bootloader (!).
+        # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence
+        # no guard against `useBootLoader`.  Examples:
+        # - testing PXE boot or other EFI applications
+        # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows
+        NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}")
+        # VM needs writable EFI vars
+        if ! test -e "$NIX_EFI_VARS"; then
+        ${if cfg.useBootLoader then
+            # We still need the EFI var from the make-disk-image derivation
+            # because our "switch-to-configuration" process might
+            # write into it and we want to keep this data.
+            ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"''
+            else
+            ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"''
+          }
+          chmod 0644 "$NIX_EFI_VARS"
         fi
-
-        NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${cfg.efiVars}}")
-
-        ${lib.optionalString cfg.useEFIBoot
-        ''
-          # VM needs writable EFI vars
-          if ! test -e "$NIX_EFI_VARS"; then
-            cp ${bootDisk}/efi-vars.fd "$NIX_EFI_VARS"
-            chmod 0644 "$NIX_EFI_VARS"
-          fi
-        ''}
       ''}
 
       cd "$TMPDIR"
@@ -200,95 +221,29 @@ let
 
   regInfo = pkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; };
 
-
-  # Generate a hard disk image containing a /boot partition and GRUB
-  # in the MBR.  Used when the `useBootLoader' option is set.
-  # Uses `runInLinuxVM` to create the image in a throwaway VM.
-  # See note [Disk layout with `useBootLoader`].
-  # FIXME: use nixos/lib/make-disk-image.nix.
-  bootDisk =
-    pkgs.vmTools.runInLinuxVM (
-      pkgs.runCommand "nixos-boot-disk"
-        { preVM =
-            ''
-              mkdir $out
-              diskImage=$out/disk.img
-              ${qemu}/bin/qemu-img create -f qcow2 $diskImage "120M"
-              ${if cfg.useEFIBoot then ''
-                efiVars=$out/efi-vars.fd
-                cp ${cfg.efi.variables} $efiVars
-                chmod 0644 $efiVars
-              '' else ""}
-            '';
-          buildInputs = [ pkgs.util-linux ];
-          QEMU_OPTS = "-nographic -serial stdio -monitor none"
-                      + lib.optionalString cfg.useEFIBoot (
-                        " -drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}"
-                      + " -drive if=pflash,format=raw,unit=1,file=$efiVars");
-        }
-        ''
-          # Create a /boot EFI partition with 120M and arbitrary but fixed GUIDs for reproducibility
-          ${pkgs.gptfdisk}/bin/sgdisk \
-            --set-alignment=1 --new=1:34:2047 --change-name=1:BIOSBootPartition --typecode=1:ef02 \
-            --set-alignment=512 --largest-new=2 --change-name=2:EFISystem --typecode=2:ef00 \
-            --attributes=1:set:1 \
-            --attributes=2:set:2 \
-            --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C1 \
-            --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
-            --partition-guid=2:970C694F-AFD0-4B99-B750-CDB7A329AB6F \
-            --hybrid 2 \
-            --recompute-chs /dev/vda
-
-          ${optionalString (config.boot.loader.grub.device != "/dev/vda")
-            # In this throwaway VM, we only have the /dev/vda disk, but the
-            # actual VM described by `config` (used by `switch-to-configuration`
-            # below) may set `boot.loader.grub.device` to a different device
-            # that's nonexistent in the throwaway VM.
-            # Create a symlink for that device, so that the `grub-install`
-            # by `switch-to-configuration` will hit /dev/vda anyway.
-            ''
-              ln -s /dev/vda ${config.boot.loader.grub.device}
-            ''
-          }
-
-          ${pkgs.dosfstools}/bin/mkfs.fat -F16 /dev/vda2
-          export MTOOLS_SKIP_CHECK=1
-          ${pkgs.mtools}/bin/mlabel -i /dev/vda2 ::boot
-
-          # Mount /boot; load necessary modules first.
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/nls/nls_cp437.ko.xz || true
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/nls/nls_iso8859-1.ko.xz || true
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/fat/fat.ko.xz || true
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/fat/vfat.ko.xz || true
-          ${pkgs.kmod}/bin/insmod ${pkgs.linux}/lib/modules/*/kernel/fs/efivarfs/efivarfs.ko.xz || true
-          mkdir /boot
-          mount /dev/vda2 /boot
-
-          ${optionalString config.boot.loader.efi.canTouchEfiVariables ''
-            mount -t efivarfs efivarfs /sys/firmware/efi/efivars
-          ''}
-
-          # This is needed for GRUB 0.97, which doesn't know about virtio devices.
-          mkdir /boot/grub
-          echo '(hd0) /dev/vda' > /boot/grub/device.map
-
-          # This is needed for systemd-boot to find ESP, and udev is not available here to create this
-          mkdir -p /dev/block
-          ln -s /dev/vda2 /dev/block/254:2
-
-          # Set up system profile (normally done by nixos-rebuild / nix-env --set)
-          mkdir -p /nix/var/nix/profiles
-          ln -s ${config.system.build.toplevel} /nix/var/nix/profiles/system-1-link
-          ln -s /nix/var/nix/profiles/system-1-link /nix/var/nix/profiles/system
-
-          # Install bootloader
-          touch /etc/NIXOS
-          export NIXOS_INSTALL_BOOTLOADER=1
-          ${config.system.build.toplevel}/bin/switch-to-configuration boot
-
-          umount /boot
-        '' # */
-    );
+  # System image is akin to a complete NixOS install with
+  # a boot partition and root partition.
+  systemImage = import ../../lib/make-disk-image.nix {
+    inherit pkgs config lib;
+    additionalPaths = [ regInfo ];
+    format = "qcow2";
+    onlyNixStore = false;
+    partitionTableType = selectPartitionTableLayout { inherit (cfg) useDefaultFilesystems useEFIBoot; };
+    # Bootloader should be installed on the system image only if we are booting through bootloaders.
+    # Though, if a user is not using our default filesystems, it is possible to not have any ESP
+    # or a strange partition table that's incompatible with GRUB configuration.
+    # As a consequence, this may lead to disk image creation failures.
+    # To avoid this, we prefer to let the user find out about how to install the bootloader on its ESP/disk.
+    # Usually, this can be through building your own disk image.
+    # TODO: If a user is interested into a more fine grained heuristic for `installBootLoader`
+    # by examining the actual contents of `cfg.fileSystems`, please send a PR.
+    installBootLoader = cfg.useBootLoader && cfg.useDefaultFilesystems;
+    touchEFIVars = cfg.useEFIBoot;
+    diskSize = "auto";
+    additionalSpace = "0M";
+    copyChannel = false;
+    OVMF = cfg.efi.OVMF;
+  };
 
   storeImage = import ../../lib/make-disk-image.nix {
     inherit pkgs config lib;
@@ -297,17 +252,42 @@ let
     onlyNixStore = true;
     partitionTableType = "none";
     installBootLoader = false;
+    touchEFIVars = false;
     diskSize = "auto";
     additionalSpace = "0M";
     copyChannel = false;
   };
 
+  bootConfiguration =
+    if cfg.useDefaultFilesystems
+    then
+      if cfg.useBootLoader
+      then
+        if cfg.useEFIBoot then "efi_bootloading_with_default_fs"
+        else "legacy_bootloading_with_default_fs"
+      else
+        "direct_boot_with_default_fs"
+    else
+      "custom";
+  suggestedRootDevice = {
+    "efi_bootloading_with_default_fs" = "${cfg.bootLoaderDevice}2";
+    "legacy_bootloading_with_default_fs" = "${cfg.bootLoaderDevice}1";
+    "direct_boot_with_default_fs" = cfg.bootLoaderDevice;
+    # This will enforce a NixOS module type checking error
+    # to ask explicitly the user to set a rootDevice.
+    # As it will look like `rootDevice = lib.mkDefault null;` after
+    # all "computations".
+    "custom" = null;
+  }.${bootConfiguration};
 in
 
 {
   imports = [
     ../profiles/qemu-guest.nix
     (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ])
+    (mkRemovedOptionModule [ "virtualisation" "bootDevice" ] "This option was renamed to `virtualisation.rootDevice`, as it was incorrectly named and misleading. Take the time to review what you want to do and look at the new options like `virtualisation.{bootLoaderDevice, bootPartition}`, open an issue in case of issues.")
+    (mkRemovedOptionModule [ "virtualisation" "efiVars" ] "This option was removed, it is possible to provide a template UEFI variable with `virtualisation.efi.variables` ; if this option is important to you, open an issue")
+    (mkRemovedOptionModule [ "virtualisation" "persistBootDevice" ] "Boot device is always persisted if you use a bootloader through the root disk image ; if this does not work for your usecase, please examine carefully what `virtualisation.{bootDevice, rootDevice, bootPartition}` options offer you and open an issue explaining your need.`")
   ];
 
   options = {
@@ -362,25 +342,48 @@ in
           '';
       };
 
-    virtualisation.bootDevice =
+    virtualisation.bootLoaderDevice =
       mkOption {
         type = types.path;
+        default = lookupDriveDeviceName "root" cfg.qemu.drives;
+        defaultText = literalExpression ''lookupDriveDeviceName "root" cfg.qemu.drives'';
         example = "/dev/vda";
         description =
           lib.mdDoc ''
-            The disk to be used for the root filesystem.
+            The disk to be used for the boot filesystem.
+            By default, it is the same disk as the root filesystem.
+          '';
+        };
+
+    virtualisation.bootPartition =
+      mkOption {
+        type = types.nullOr types.path;
+        default = if cfg.useEFIBoot then "${cfg.bootLoaderDevice}1" else null;
+        defaultText = literalExpression ''if cfg.useEFIBoot then "''${cfg.bootLoaderDevice}1" else null'';
+        example = "/dev/vda1";
+        description =
+          lib.mdDoc ''
+            The boot partition to be used to mount /boot filesystem.
+            In legacy boots, this should be null.
+            By default, in EFI boot, it is the first partition of the boot device.
           '';
       };
 
-    virtualisation.persistBootDevice =
+    virtualisation.rootDevice =
       mkOption {
-        type = types.bool;
-        default = false;
+        type = types.nullOr types.path;
+        example = "/dev/vda2";
         description =
           lib.mdDoc ''
-            If useBootLoader is specified, whether to recreate the boot device
-            on each instantiaton or allow it to persist.
-            '';
+            The disk or partition to be used for the root filesystem.
+            By default (read the source code for more details):
+
+            - under EFI with a bootloader: 2nd partition of the boot disk
+            - in legacy boot with a bootloader: 1st partition of the boot disk
+            - in direct boot (i.e. without a bootloader): whole disk
+
+            In case you are not using a default boot device or a default filesystem, you have to set explicitly your root device.
+          '';
       };
 
     virtualisation.emptyDiskImages =
@@ -579,12 +582,15 @@ in
     virtualisation.writableStore =
       mkOption {
         type = types.bool;
-        default = true; # FIXME
+        default = cfg.mountHostNixStore;
+        defaultText = literalExpression "cfg.mountHostNixStore";
         description =
           lib.mdDoc ''
             If enabled, the Nix store in the VM is made writable by
             layering an overlay filesystem on top of the host's Nix
             store.
+
+            By default, this is enabled if you mount a host Nix store.
           '';
       };
 
@@ -718,6 +724,21 @@ in
           For applications which do a lot of reads from the store,
           this can drastically improve performance, but at the cost of
           disk space and image build time.
+
+          As an alternative, you can use a bootloader which will provide you
+          with a full NixOS system image containing a Nix store and
+          avoid mounting the host nix store through
+          {option}`virtualisation.mountHostNixStore`.
+        '';
+      };
+
+    virtualisation.mountHostNixStore =
+      mkOption {
+        type = types.bool;
+        default = !cfg.useNixStoreImage && !cfg.useBootLoader;
+        defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader";
+        description = lib.mdDoc ''
+          Mount the host Nix store as a 9p mount.
         '';
       };
 
@@ -749,10 +770,22 @@ in
         };
 
     virtualisation.efi = {
+      OVMF = mkOption {
+        type = types.package;
+        default = (pkgs.OVMF.override {
+          secureBoot = cfg.useSecureBoot;
+        }).fd;
+        defaultText = ''(pkgs.OVMF.override {
+          secureBoot = cfg.useSecureBoot;
+        }).fd'';
+        description =
+        lib.mdDoc "OVMF firmware package, defaults to OVMF configured with secure boot if needed.";
+      };
+
       firmware = mkOption {
         type = types.path;
-        default = pkgs.OVMF.firmware;
-        defaultText = literalExpression "pkgs.OVMF.firmware";
+        default = cfg.efi.OVMF.firmware;
+        defaultText = literalExpression "cfg.efi.OVMF.firmware";
         description =
           lib.mdDoc ''
             Firmware binary for EFI implementation, defaults to OVMF.
@@ -761,8 +794,8 @@ in
 
       variables = mkOption {
         type = types.path;
-        default = pkgs.OVMF.variables;
-        defaultText = literalExpression "pkgs.OVMF.variables";
+        default = cfg.efi.OVMF.variables;
+        defaultText = literalExpression "cfg.efi.OVMF.variables";
         description =
           lib.mdDoc ''
             Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware.
@@ -786,18 +819,17 @@ in
           '';
       };
 
-    virtualisation.efiVars =
+    virtualisation.useSecureBoot =
       mkOption {
-        type = types.str;
-        default = "./${config.system.name}-efi-vars.fd";
-        defaultText = literalExpression ''"./''${config.system.name}-efi-vars.fd"'';
+        type = types.bool;
+        default = false;
         description =
           lib.mdDoc ''
-            Path to nvram image containing UEFI variables.  The will be created
-            on startup if it does not exist.
+            Enable Secure Boot support in the EFI firmware.
           '';
       };
 
+
     virtualisation.bios =
       mkOption {
         type = types.nullOr types.package;
@@ -853,33 +885,18 @@ in
             ${opt.writableStore} = false;
         '';
 
-    # Note [Disk layout with `useBootLoader`]
-    #
-    # If `useBootLoader = true`, we configure 2 drives:
-    # `/dev/?da` for the root disk, and `/dev/?db` for the boot disk
-    # which has the `/boot` partition and the boot loader.
-    # Concretely:
-    #
-    # * The second drive's image `disk.img` is created in `bootDisk = ...`
-    #   using a throwaway VM. Note that there the disk is always `/dev/vda`,
-    #   even though in the final VM it will be at `/dev/*b`.
-    # * The disks are attached in `virtualisation.qemu.drives`.
-    #   Their order makes them appear as devices `a`, `b`, etc.
-    # * `fileSystems."/boot"` is adjusted to be on device `b`.
-    # * The disk.img is recreated each time the VM is booted unless
-    #   virtualisation.persistBootDevice is set.
-
-    # If `useBootLoader`, GRUB goes to the second disk, see
-    # note [Disk layout with `useBootLoader`].
-    boot.loader.grub.device = mkVMOverride (
-      if cfg.useBootLoader
-        then driveDeviceName 2 # second disk
-        else cfg.bootDevice
-    );
+    # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install
+    # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs.
+    # Otherwise, we set the proper bootloader device for this.
+    # FIXME: make a sense of this mess wrt to multiple ESP present in the system, probably use boot.efiSysMountpoint?
+    boot.loader.grub.device = mkVMOverride (if cfg.useEFIBoot then "nodev" else cfg.bootLoaderDevice);
     boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}";
+    virtualisation.rootDevice = mkDefault suggestedRootDevice;
 
     boot.initrd.kernelModules = optionals (cfg.useNixStoreImage && !cfg.writableStore) [ "erofs" ];
 
+    boot.loader.supportsInitrdSecrets = mkIf (!cfg.useBootLoader) (mkVMOverride false);
+
     boot.initrd.extraUtilsCommands = lib.mkIf (cfg.useDefaultFilesystems && !config.boot.initrd.systemd.enable)
       ''
         # We need mke2fs in the initrd.
@@ -890,10 +907,10 @@ in
       ''
         # If the disk image appears to be empty, run mke2fs to
         # initialise.
-        FSTYPE=$(blkid -o value -s TYPE ${cfg.bootDevice} || true)
-        PARTTYPE=$(blkid -o value -s PTTYPE ${cfg.bootDevice} || true)
+        FSTYPE=$(blkid -o value -s TYPE ${cfg.rootDevice} || true)
+        PARTTYPE=$(blkid -o value -s PTTYPE ${cfg.rootDevice} || true)
         if test -z "$FSTYPE" -a -z "$PARTTYPE"; then
-            mke2fs -t ext4 ${cfg.bootDevice}
+            mke2fs -t ext4 ${cfg.rootDevice}
         fi
       '';
 
@@ -939,12 +956,10 @@ in
       optional cfg.writableStore "overlay"
       ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx";
 
-    virtualisation.bootDevice = mkDefault (driveDeviceName 1);
-
     virtualisation.additionalPaths = [ config.system.build.toplevel ];
 
     virtualisation.sharedDirectories = {
-      nix-store = mkIf (!cfg.useNixStoreImage) {
+      nix-store = mkIf cfg.mountHostNixStore {
         source = builtins.storeDir;
         target = "/nix/store";
       };
@@ -997,7 +1012,7 @@ in
       ])
       (mkIf cfg.useEFIBoot [
         "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}"
-        "-drive if=pflash,format=raw,unit=1,file=$NIX_EFI_VARS"
+        "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS"
       ])
       (mkIf (cfg.bios != null) [
         "-bios ${cfg.bios}/bios.bin"
@@ -1013,23 +1028,14 @@ in
         file = ''"$NIX_DISK_IMAGE"'';
         driveExtraOpts.cache = "writeback";
         driveExtraOpts.werror = "report";
+        deviceExtraOpts.bootindex = "1";
       }])
       (mkIf cfg.useNixStoreImage [{
         name = "nix-store";
         file = ''"$TMPDIR"/store.img'';
-        deviceExtraOpts.bootindex = if cfg.useBootLoader then "3" else "2";
+        deviceExtraOpts.bootindex = "2";
         driveExtraOpts.format = if cfg.writableStore then "qcow2" else "raw";
       }])
-      (mkIf cfg.useBootLoader [
-        # The order of this list determines the device names, see
-        # note [Disk layout with `useBootLoader`].
-        {
-          name = "boot";
-          file = ''"$TMPDIR"/disk.img'';
-          driveExtraOpts.media = "disk";
-          deviceExtraOpts.bootindex = "1";
-        }
-      ])
       (imap0 (idx: _: {
         file = "$(pwd)/empty${toString idx}.qcow2";
         driveExtraOpts.werror = "report";
@@ -1065,7 +1071,7 @@ in
           device = "tmpfs";
           fsType = "tmpfs";
         } else {
-          device = cfg.bootDevice;
+          device = cfg.rootDevice;
           fsType = "ext4";
           autoFormat = true;
         });
@@ -1086,9 +1092,8 @@ in
           options = [ "mode=0755" ];
           neededForBoot = true;
         };
-        # see note [Disk layout with `useBootLoader`]
-        "/boot" = lib.mkIf cfg.useBootLoader {
-          device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk`
+        "/boot" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) {
+          device = cfg.bootPartition; # 1 for e.g. `vda1`, as created in `systemImage`
           fsType = "vfat";
           noCheck = true; # fsck fails on a r/o filesystem
         };
diff --git a/nixos/modules/virtualisation/rosetta.nix b/nixos/modules/virtualisation/rosetta.nix
index 109b114d649c5..ee811b571b8f8 100644
--- a/nixos/modules/virtualisation/rosetta.nix
+++ b/nixos/modules/virtualisation/rosetta.nix
@@ -50,11 +50,19 @@ in
       }
     ];
 
-    fileSystems."${cfg.mountPoint}" =  {
+    fileSystems."${cfg.mountPoint}" = {
       device = cfg.mountTag;
       fsType = "virtiofs";
     };
 
+
+    nix.settings = {
+      extra-platforms = [ "x86_64-linux" ];
+      extra-sandbox-paths =  [
+        "/run/binfmt"
+        cfg.mountPoint
+      ];
+    };
     boot.binfmt.registrations.rosetta = {
       interpreter = "${cfg.mountPoint}/rosetta";
 
diff --git a/nixos/modules/virtualisation/xen-domU.nix b/nixos/modules/virtualisation/xen-domU.nix
index c00b984c2ce04..ce5a482b1145b 100644
--- a/nixos/modules/virtualisation/xen-domU.nix
+++ b/nixos/modules/virtualisation/xen-domU.nix
@@ -3,7 +3,6 @@
 { ... }:
 
 {
-  boot.loader.grub.version = 2;
   boot.loader.grub.device = "nodev";
 
   boot.initrd.kernelModules =
diff --git a/nixos/release.nix b/nixos/release.nix
index 78a74af41242f..93ebe000fc001 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -405,6 +405,12 @@ in rec {
         services.xserver.desktopManager.pantheon.enable = true;
       });
 
+    deepin = makeClosure ({ ... }:
+      { services.xserver.enable = true;
+        services.xserver.displayManager.lightdm.enable = true;
+        services.xserver.desktopManager.deepin.enable = true;
+      });
+
     # Linux/Apache/PostgreSQL/PHP stack.
     lapp = makeClosure ({ pkgs, ... }:
       { services.httpd.enable = true;
diff --git a/nixos/tests/aaaaxy.nix b/nixos/tests/aaaaxy.nix
new file mode 100644
index 0000000000000..90b3c85e0c5f2
--- /dev/null
+++ b/nixos/tests/aaaaxy.nix
@@ -0,0 +1,29 @@
+{ pkgs, lib, ... }: {
+  name = "aaaaxy";
+  meta.maintainers = with lib.maintainers; [ Luflosi ];
+
+  nodes.machine = {
+    imports = [
+      ./common/x11.nix
+    ];
+  };
+
+  # This starts the game from a known state, feeds it a prerecorded set of button presses
+  # and then checks if the final game state is identical to the expected state.
+  # This is also what AAAAXY's CI system does and serves as a good sanity check.
+  testScript = ''
+    machine.wait_for_x()
+
+    machine.succeed(
+      # benchmark.dem needs to be in a mutable directory,
+      # so we can't just refer to the file in the Nix store directly
+      "mkdir -p '/tmp/aaaaxy/assets/demos/'",
+      "ln -s '${pkgs.aaaaxy.testing_infra}/assets/demos/benchmark.dem' '/tmp/aaaaxy/assets/demos/'",
+      """
+        '${pkgs.aaaaxy.testing_infra}/scripts/regression-test-demo.sh' \
+        'aaaaxy' 'on track for Any%, All Paths and No Teleports' \
+        '${pkgs.aaaaxy}/bin/aaaaxy' '/tmp/aaaaxy/assets/demos/benchmark.dem'
+      """,
+    )
+  '';
+}
diff --git a/nixos/tests/alice-lg.nix b/nixos/tests/alice-lg.nix
new file mode 100644
index 0000000000000..640e60030a04e
--- /dev/null
+++ b/nixos/tests/alice-lg.nix
@@ -0,0 +1,44 @@
+# This test does a basic functionality check for alice-lg
+
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) optionalString;
+in
+makeTest {
+  name = "birdwatcher";
+  nodes = {
+    host1 = {
+      environment.systemPackages = with pkgs; [ jq ];
+      services.alice-lg = {
+        enable = true;
+        settings = {
+          server = {
+            listen_http = "[::]:7340";
+            enable_prefix_lookup = true;
+            asn = 1;
+            routes_store_refresh_parallelism = 5;
+            neighbors_store_refresh_parallelism = 10000;
+            routes_store_refresh_interval = 5;
+            neighbors_store_refresh_interval = 5;
+          };
+          housekeeping = {
+            interval = 5;
+            force_release_memory = true;
+          };
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    host1.wait_for_unit("alice-lg.service")
+    host1.wait_for_open_port(7340)
+    host1.succeed("curl http://[::]:7340 | grep 'Alice BGP Looking Glass'")
+  '';
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index b45db283bd6f1..81c5e8dd9f3aa 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -46,7 +46,7 @@ let
   inherit
     (rec {
       doRunTest = arg: ((import ../lib/testing-python.nix { inherit system pkgs; }).evalTest {
-        imports = [ arg ];
+        imports = [ arg readOnlyPkgs ];
       }).config.result;
       findTests = tree:
         if tree?recurseForDerivations && tree.recurseForDerivations
@@ -65,8 +65,35 @@ let
     runTestOn
     ;
 
+  # Using a single instance of nixpkgs makes test evaluation faster.
+  # To make sure we don't accidentally depend on a modified pkgs, we make the
+  # related options read-only. We need to test the right configuration.
+  #
+  # If your service depends on a nixpkgs setting, first try to avoid that, but
+  # otherwise, you can remove the readOnlyPkgs import and test your service as
+  # usual.
+  readOnlyPkgs =
+    # TODO: We currently accept this for nixosTests, so that the `pkgs` argument
+    #       is consistent with `pkgs` in `pkgs.nixosTests`. Can we reinitialize
+    #       it with `allowAliases = false`?
+    # warnIf pkgs.config.allowAliases "nixosTests: pkgs includes aliases."
+    {
+      _class = "nixosTest";
+      node.pkgs = pkgs;
+    };
+
 in {
+
+  # Testing the test driver
+  nixos-test-driver = {
+    extra-python-packages = handleTest ./nixos-test-driver/extra-python-packages.nix {};
+    node-name = runTest ./nixos-test-driver/node-name.nix;
+  };
+
+  # NixOS vm tests and non-vm unit tests
+
   _3proxy = runTest ./3proxy.nix;
+  aaaaxy = runTest ./aaaaxy.nix;
   acme = runTest ./acme.nix;
   adguardhome = runTest ./adguardhome.nix;
   aesmd = runTestOn ["x86_64-linux"] ./aesmd.nix;
@@ -75,6 +102,7 @@ in {
   airsonic = handleTest ./airsonic.nix {};
   akkoma = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./akkoma.nix {};
   akkoma-confined = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./akkoma.nix { confined = true; };
+  alice-lg = handleTest ./alice-lg.nix {};
   allTerminfo = handleTest ./all-terminfo.nix {};
   alps = handleTest ./alps.nix {};
   amazon-init-shell = handleTest ./amazon-init-shell.nix {};
@@ -96,6 +124,7 @@ in {
   binary-cache = handleTest ./binary-cache.nix {};
   bind = handleTest ./bind.nix {};
   bird = handleTest ./bird.nix {};
+  birdwatcher = handleTest ./birdwatcher.nix {};
   bitcoind = handleTest ./bitcoind.nix {};
   bittorrent = handleTest ./bittorrent.nix {};
   blockbook-frontend = handleTest ./blockbook-frontend.nix {};
@@ -145,6 +174,7 @@ in {
   collectd = handleTest ./collectd.nix {};
   connman = handleTest ./connman.nix {};
   consul = handleTest ./consul.nix {};
+  consul-template = handleTest ./consul-template.nix {};
   containers-bridge = handleTest ./containers-bridge.nix {};
   containers-custom-pkgs.nix = handleTest ./containers-custom-pkgs.nix {};
   containers-ephemeral = handleTest ./containers-ephemeral.nix {};
@@ -169,6 +199,8 @@ in {
   cups-pdf = handleTest ./cups-pdf.nix {};
   custom-ca = handleTest ./custom-ca.nix {};
   croc = handleTest ./croc.nix {};
+  darling = handleTest ./darling.nix {};
+  deepin = handleTest ./deepin.nix {};
   deluge = handleTest ./deluge.nix {};
   dendrite = handleTest ./matrix/dendrite.nix {};
   dex-oidc = handleTest ./dex-oidc.nix {};
@@ -194,6 +226,7 @@ in {
   dovecot = handleTest ./dovecot.nix {};
   drbd = handleTest ./drbd.nix {};
   earlyoom = handleTestOn ["x86_64-linux"] ./earlyoom.nix {};
+  early-mount-options = handleTest ./early-mount-options.nix {};
   ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
   ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
   ecryptfs = handleTest ./ecryptfs.nix {};
@@ -217,7 +250,6 @@ in {
   etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
   etebase-server = handleTest ./etebase-server.nix {};
   etesync-dav = handleTest ./etesync-dav.nix {};
-  extra-python-packages = handleTest ./extra-python-packages.nix {};
   evcc = handleTest ./evcc.nix {};
   fancontrol = handleTest ./fancontrol.nix {};
   fcitx5 = handleTest ./fcitx5 {};
@@ -254,7 +286,7 @@ in {
   gitdaemon = handleTest ./gitdaemon.nix {};
   gitea = handleTest ./gitea.nix { giteaPackage = pkgs.gitea; };
   github-runner = handleTest ./github-runner.nix {};
-  gitlab = handleTest ./gitlab.nix {};
+  gitlab = runTest ./gitlab.nix;
   gitolite = handleTest ./gitolite.nix {};
   gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {};
   glusterfs = handleTest ./glusterfs.nix {};
@@ -267,6 +299,7 @@ in {
   gocd-agent = handleTest ./gocd-agent.nix {};
   gocd-server = handleTest ./gocd-server.nix {};
   gollum = handleTest ./gollum.nix {};
+  gonic = handleTest ./gonic.nix {};
   google-oslogin = handleTest ./google-oslogin {};
   gotify-server = handleTest ./gotify-server.nix {};
   grafana = handleTest ./grafana {};
@@ -283,6 +316,7 @@ in {
   haste-server = handleTest ./haste-server.nix {};
   haproxy = handleTest ./haproxy.nix {};
   hardened = handleTest ./hardened.nix {};
+  harmonia = runTest ./harmonia.nix;
   headscale = handleTest ./headscale.nix {};
   healthchecks = handleTest ./web-apps/healthchecks.nix {};
   hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; };
@@ -340,6 +374,7 @@ in {
   kafka = handleTest ./kafka.nix {};
   kanidm = handleTest ./kanidm.nix {};
   karma = handleTest ./karma.nix {};
+  kavita = handleTest ./kavita.nix {};
   kbd-setfont-decompress = handleTest ./kbd-setfont-decompress.nix {};
   kbd-update-search-paths-patch = handleTest ./kbd-update-search-paths-patch.nix {};
   kea = handleTest ./kea.nix {};
@@ -378,6 +413,7 @@ in {
   limesurvey = handleTest ./limesurvey.nix {};
   listmonk = handleTest ./listmonk.nix {};
   litestream = handleTest ./litestream.nix {};
+  lldap = handleTest ./lldap.nix {};
   locate = handleTest ./locate.nix {};
   login = handleTest ./login.nix {};
   logrotate = handleTest ./logrotate.nix {};
@@ -389,7 +425,7 @@ in {
   lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
   lorri = handleTest ./lorri/default.nix {};
-  maddy = handleTest ./maddy.nix {};
+  maddy = discoverTests (import ./maddy { inherit handleTest; });
   maestral = handleTest ./maestral.nix {};
   magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
   magnetico = handleTest ./magnetico.nix {};
@@ -398,6 +434,7 @@ in {
   man = handleTest ./man.nix {};
   mariadb-galera = handleTest ./mysql/mariadb-galera.nix {};
   mastodon = discoverTests (import ./web-apps/mastodon { inherit handleTestOn; });
+  pixelfed = discoverTests (import ./web-apps/pixelfed { inherit handleTestOn; });
   mate = handleTest ./mate.nix {};
   matomo = handleTest ./matomo.nix {};
   matrix-appservice-irc = handleTest ./matrix/appservice-irc.nix {};
@@ -421,6 +458,7 @@ in {
   mjolnir = handleTest ./matrix/mjolnir.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
   molly-brown = handleTest ./molly-brown.nix {};
+  monica = handleTest ./web-apps/monica.nix {};
   mongodb = handleTest ./mongodb.nix {};
   moodle = handleTest ./moodle.nix {};
   moonraker = handleTest ./moonraker.nix {};
@@ -526,6 +564,7 @@ in {
   pam-oath-login = handleTest ./pam/pam-oath-login.nix {};
   pam-u2f = handleTest ./pam/pam-u2f.nix {};
   pam-ussh = handleTest ./pam/pam-ussh.nix {};
+  pam-zfs-key = handleTest ./pam/zfs-key.nix {};
   pass-secret-service = handleTest ./pass-secret-service.nix {};
   patroni = handleTestOn ["x86_64-linux"] ./patroni.nix {};
   pantalaimon = handleTest ./matrix/pantalaimon.nix {};
@@ -587,6 +626,7 @@ in {
   pt2-clone = handleTest ./pt2-clone.nix {};
   pykms = handleTest ./pykms.nix {};
   public-inbox = handleTest ./public-inbox.nix {};
+  pufferpanel = handleTest ./pufferpanel.nix {};
   pulseaudio = discoverTests (import ./pulseaudio.nix);
   qboot = handleTestOn ["x86_64-linux" "i686-linux"] ./qboot.nix {};
   qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
@@ -604,6 +644,7 @@ in {
   retroarch = handleTest ./retroarch.nix {};
   robustirc-bridge = handleTest ./robustirc-bridge.nix {};
   roundcube = handleTest ./roundcube.nix {};
+  rshim = handleTest ./rshim.nix {};
   rspamd = handleTest ./rspamd.nix {};
   rss2email = handleTest ./rss2email.nix {};
   rstudio-server = handleTest ./rstudio-server.nix {};
@@ -631,6 +672,7 @@ in {
   smokeping = handleTest ./smokeping.nix {};
   snapcast = handleTest ./snapcast.nix {};
   snapper = handleTest ./snapper.nix {};
+  snipe-it = runTest ./web-apps/snipe-it.nix;
   soapui = handleTest ./soapui.nix {};
   sogo = handleTest ./sogo.nix {};
   solanum = handleTest ./solanum.nix {};
@@ -642,6 +684,7 @@ in {
   sslh = handleTest ./sslh.nix {};
   sssd = handleTestOn ["x86_64-linux"] ./sssd.nix {};
   sssd-ldap = handleTestOn ["x86_64-linux"] ./sssd-ldap.nix {};
+  stargazer = runTest ./web-servers/stargazer.nix;
   starship = handleTest ./starship.nix {};
   step-ca = handleTestOn ["x86_64-linux"] ./step-ca.nix {};
   stratis = handleTest ./stratis {};
@@ -650,6 +693,7 @@ in {
   sudo = handleTest ./sudo.nix {};
   swap-file-btrfs = handleTest ./swap-file-btrfs.nix {};
   swap-partition = handleTest ./swap-partition.nix {};
+  swap-random-encryption = handleTest ./swap-random-encryption.nix {};
   sway = handleTest ./sway.nix {};
   switchTest = handleTest ./switch-test.nix {};
   sympa = handleTest ./sympa.nix {};
@@ -677,6 +721,9 @@ in {
   systemd-initrd-simple = handleTest ./systemd-initrd-simple.nix {};
   systemd-initrd-swraid = handleTest ./systemd-initrd-swraid.nix {};
   systemd-initrd-vconsole = handleTest ./systemd-initrd-vconsole.nix {};
+  systemd-initrd-networkd = handleTest ./systemd-initrd-networkd.nix {};
+  systemd-initrd-networkd-ssh = handleTest ./systemd-initrd-networkd-ssh.nix {};
+  systemd-initrd-networkd-openvpn = handleTest ./initrd-network-openvpn { systemdStage1 = true; };
   systemd-journal = handleTest ./systemd-journal.nix {};
   systemd-machinectl = handleTest ./systemd-machinectl.nix {};
   systemd-networkd = handleTest ./systemd-networkd.nix {};
@@ -706,6 +753,7 @@ in {
   tiddlywiki = handleTest ./tiddlywiki.nix {};
   tigervnc = handleTest ./tigervnc.nix {};
   timescaledb = handleTest ./timescaledb.nix {};
+  promscale = handleTest ./promscale.nix {};
   timezone = handleTest ./timezone.nix {};
   tinc = handleTest ./tinc {};
   tinydns = handleTest ./tinydns.nix {};
@@ -743,6 +791,7 @@ in {
   varnish60 = handleTest ./varnish.nix { package = pkgs.varnish60; };
   varnish72 = handleTest ./varnish.nix { package = pkgs.varnish72; };
   vault = handleTest ./vault.nix {};
+  vault-agent = handleTest ./vault-agent.nix {};
   vault-dev = handleTest ./vault-dev.nix {};
   vault-postgresql = handleTest ./vault-postgresql.nix {};
   vaultwarden = handleTest ./vaultwarden.nix {};
diff --git a/nixos/tests/birdwatcher.nix b/nixos/tests/birdwatcher.nix
new file mode 100644
index 0000000000000..5c41b4d0e4f3a
--- /dev/null
+++ b/nixos/tests/birdwatcher.nix
@@ -0,0 +1,94 @@
+# This test does a basic functionality check for birdwatcher
+
+{ system ? builtins.currentSystem
+, pkgs ? import ../.. { inherit system; config = { }; }
+}:
+
+let
+  inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) optionalString;
+in
+makeTest {
+  name = "birdwatcher";
+  nodes = {
+    host1 = {
+      environment.systemPackages = with pkgs; [ jq ];
+      services.bird2 = {
+        enable = true;
+        config = ''
+          log syslog all;
+
+          debug protocols all;
+
+          router id 10.0.0.1;
+
+          protocol device {
+          }
+
+          protocol kernel kernel4 {
+            ipv4 {
+              import none;
+              export all;
+            };
+          }
+
+          protocol kernel kernel6 {
+            ipv6 {
+              import none;
+              export all;
+            };
+          }
+        '';
+      };
+      services.birdwatcher = {
+        enable = true;
+        settings = ''
+          [server]
+          allow_from = []
+          allow_uncached = false
+          modules_enabled = ["status",
+                             "protocols",
+                             "protocols_bgp",
+                             "protocols_short",
+                             "routes_protocol",
+                             "routes_peer",
+                             "routes_table",
+                             "routes_table_filtered",
+                             "routes_table_peer",
+                             "routes_filtered",
+                             "routes_prefixed",
+                             "routes_noexport",
+                             "routes_pipe_filtered_count",
+                             "routes_pipe_filtered"
+                            ]
+          [status]
+          reconfig_timestamp_source = "bird"
+          reconfig_timestamp_match = "# created: (.*)"
+          filter_fields = []
+          [bird]
+          listen = "0.0.0.0:29184"
+          config = "/etc/bird/bird2.conf"
+          birdc  = "${pkgs.bird}/bin/birdc"
+          ttl = 5 # time to live (in minutes) for caching of cli output
+          [parser]
+          filter_fields = []
+          [cache]
+          use_redis = false # if not using redis cache, activate housekeeping to save memory!
+          [housekeeping]
+          interval = 5
+          force_release_memory = true
+        '';
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    host1.wait_for_unit("bird2.service")
+    host1.wait_for_unit("birdwatcher.service")
+    host1.wait_for_open_port(29184)
+    host1.succeed("curl http://[::]:29184/status | jq -r .status.message | grep 'Daemon is up and running'")
+    host1.succeed("curl http://[::]:29184/protocols | jq -r .protocols.device1.state | grep 'up'")
+  '';
+}
diff --git a/nixos/tests/bootspec.nix b/nixos/tests/bootspec.nix
index 077dff918e0d2..9295500422a92 100644
--- a/nixos/tests/bootspec.nix
+++ b/nixos/tests/bootspec.nix
@@ -108,9 +108,9 @@ in
       machine.start()
       machine.wait_for_unit("multi-user.target")
 
-      machine.succeed("test -e /run/current-system/bootspec/boot.json")
+      machine.succeed("test -e /run/current-system/boot.json")
 
-      bootspec = json.loads(machine.succeed("jq -r '.v1' /run/current-system/bootspec/boot.json"))
+      bootspec = json.loads(machine.succeed("jq -r '.\"org.nixos.bootspec.v1\"' /run/current-system/boot.json"))
 
       assert all(key in bootspec for key in ('initrd', 'initrdSecrets')), "Bootspec should contain initrd or initrdSecrets field when initrd is enabled"
     '';
@@ -136,10 +136,10 @@ in
       machine.succeed("test -e /run/current-system/boot.json")
       machine.succeed("test -e /run/current-system/specialisation/something/boot.json")
 
-      sp_in_parent = json.loads(machine.succeed("jq -r '.v1.specialisation.something' /run/current-system/boot.json"))
+      sp_in_parent = json.loads(machine.succeed("jq -r '.\"org.nixos.specialisation.v1\".something' /run/current-system/boot.json"))
       sp_in_fs = json.loads(machine.succeed("cat /run/current-system/specialisation/something/boot.json"))
 
-      assert sp_in_parent == sp_in_fs['v1'], "Bootspecs of the same specialisation are different!"
+      assert sp_in_parent['org.nixos.bootspec.v1'] == sp_in_fs['org.nixos.bootspec.v1'], "Bootspecs of the same specialisation are different!"
     '';
   };
 
@@ -152,7 +152,9 @@ in
       imports = [ standard ];
       environment.systemPackages = [ pkgs.jq ];
       boot.bootspec.extensions = {
-        osRelease = config.environment.etc."os-release".source;
+        "org.nix-tests.product" = {
+          osRelease = config.environment.etc."os-release".source;
+        };
       };
     };
 
@@ -161,7 +163,7 @@ in
       machine.wait_for_unit("multi-user.target")
 
       current_os_release = machine.succeed("cat /etc/os-release")
-      bootspec_os_release = machine.succeed("cat $(jq -r '.v1.extensions.osRelease' /run/current-system/boot.json)")
+      bootspec_os_release = machine.succeed("cat $(jq -r '.\"org.nix-tests.product\".osRelease' /run/current-system/boot.json)")
 
       assert current_os_release == bootspec_os_release, "Filename referenced by extension has unexpected contents"
     '';
diff --git a/nixos/tests/bpf.nix b/nixos/tests/bpf.nix
index 5dc97404772be..150ed0958862e 100644
--- a/nixos/tests/bpf.nix
+++ b/nixos/tests/bpf.nix
@@ -26,8 +26,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         "    printf(\"tgid: %d\", ((struct task_struct*) curtask)->tgid); exit() "
         "}'"))
     # module BTF (bpftrace >= 0.17)
-    print(machine.succeed("bpftrace -e 'kfunc:nft_trans_alloc_gfp { "
-        "    printf(\"portid: %d\\n\",args->ctx->portid); "
+    # test is currently disabled on aarch64 as kfunc does not work there yet
+    # https://github.com/iovisor/bpftrace/issues/2496
+    print(machine.succeed("uname -m | grep aarch64 || "
+        "bpftrace -e 'kfunc:nft_trans_alloc_gfp { "
+        "    printf(\"portid: %d\\n\", args->ctx->portid); "
         "} BEGIN { exit() }'"))
   '';
 })
diff --git a/nixos/tests/budgie.nix b/nixos/tests/budgie.nix
index b9eba74b450bd..8fe32bb26fbd5 100644
--- a/nixos/tests/budgie.nix
+++ b/nixos/tests/budgie.nix
@@ -20,7 +20,12 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       };
     };
 
-    services.xserver.desktopManager.budgie.enable = true;
+    services.xserver.desktopManager.budgie = {
+      enable = true;
+      extraPlugins = [
+        pkgs.budgiePlugins.budgie-analogue-clock-applet
+      ];
+    };
   };
 
   testScript = { nodes, ... }:
diff --git a/nixos/tests/consul-template.nix b/nixos/tests/consul-template.nix
new file mode 100644
index 0000000000000..cbffa94569e38
--- /dev/null
+++ b/nixos/tests/consul-template.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "consul-template";
+
+  nodes.machine = { ... }: {
+    services.consul-template.instances.example.settings = {
+      template = [{
+        contents = ''
+          {{ key "example" }}
+        '';
+        perms = "0600";
+        destination = "/example";
+      }];
+    };
+
+    services.consul = {
+      enable = true;
+      extraConfig = {
+        server = true;
+        bootstrap_expect = 1;
+        bind_addr = "127.0.0.1";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("consul.service")
+    machine.wait_for_open_port(8500)
+
+    machine.wait_for_unit("consul-template-example.service")
+
+    machine.wait_until_succeeds('consul kv put example example')
+
+    machine.wait_for_file("/example")
+    machine.succeed('grep "example" /example')
+  '';
+})
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 3007efaf88710..22b664a90e170 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -25,6 +25,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
               system.stateVersion = "18.03";
             };
           };
+
+          # The system is inherited from the host above.
+          # Set it to null, to remove the "legacy" entrypoint's non-hermetic default.
+          system = null;
         };
       in with pkgs; [
         stdenv stdenvNoCC emptyContainer.config.containers.foo.path
diff --git a/nixos/tests/darling.nix b/nixos/tests/darling.nix
new file mode 100644
index 0000000000000..5665b4c2ffef2
--- /dev/null
+++ b/nixos/tests/darling.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }:
+
+let
+  # Well, we _can_ cross-compile from Linux :)
+  hello = pkgs.runCommand "hello" {
+    sdk = "${pkgs.darling.sdk}/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk";
+    nativeBuildInputs = with pkgs.llvmPackages_14; [ clang-unwrapped lld ];
+    src = pkgs.writeText "hello.c" ''
+      #include <stdio.h>
+      int main() {
+        printf("Hello, Darling!\n");
+        return 0;
+      }
+    '';
+  } ''
+    clang \
+      -target x86_64-apple-darwin \
+      -fuse-ld=lld \
+      -nostdinc -nostdlib \
+      -mmacosx-version-min=10.15 \
+      --sysroot $sdk \
+      -isystem $sdk/usr/include \
+      -L $sdk/usr/lib -lSystem \
+      $src -o $out
+  '';
+in
+{
+  name = "darling";
+
+  meta.maintainers = with lib.maintainers; [ zhaofengli ];
+
+  nodes.machine = {
+    programs.darling.enable = true;
+  };
+
+  testScript = ''
+    start_all()
+
+    # Darling holds stdout until the server is shutdown
+    machine.succeed("darling ${hello} >hello.out")
+    machine.succeed("grep Hello hello.out")
+    machine.succeed("darling shutdown")
+  '';
+})
diff --git a/nixos/tests/deepin.nix b/nixos/tests/deepin.nix
new file mode 100644
index 0000000000000..0fe93486b6cb1
--- /dev/null
+++ b/nixos/tests/deepin.nix
@@ -0,0 +1,57 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "deepin";
+
+  meta = with lib; {
+    maintainers = teams.deepin.members;
+  };
+
+  nodes.machine = { ... }: {
+    imports = [
+      ./common/user-account.nix
+    ];
+
+    services.xserver.enable = true;
+
+    services.xserver.displayManager = {
+      lightdm.enable = true;
+      autoLogin = {
+        enable = true;
+        user = "alice";
+      };
+    };
+
+    services.xserver.desktopManager.deepin.enable = true;
+  };
+
+  testScript = { nodes, ... }:
+    let
+      user = nodes.machine.users.users.alice;
+    in
+    ''
+      with subtest("Wait for login"):
+          machine.wait_for_x()
+          machine.wait_for_file("${user.home}/.Xauthority")
+          machine.succeed("xauth merge ${user.home}/.Xauthority")
+
+      with subtest("Check that logging in has given the user ownership of devices"):
+          machine.succeed("getfacl -p /dev/snd/timer | grep -q ${user.name}")
+
+      with subtest("Check if DDE wm chooser actually start"):
+          machine.wait_until_succeeds("pgrep -f dde-wm-chooser")
+          machine.wait_for_window("dde-wm-chooser")
+          machine.execute("pkill dde-wm-chooser")
+
+
+      with subtest("Check if Deepin session components actually start"):
+          machine.wait_until_succeeds("pgrep -f dde-session-daemon")
+          machine.wait_for_window("dde-session-daemon")
+          machine.wait_until_succeeds("pgrep -f dde-desktop")
+          machine.wait_for_window("dde-desktop")
+
+      with subtest("Open deepin-terminal"):
+          machine.succeed("su - ${user.name} -c 'DISPLAY=:0 deepin-terminal >&2 &'")
+          machine.wait_for_window("deepin-terminal")
+          machine.sleep(20)
+          machine.screenshot("screen")
+    '';
+})
diff --git a/nixos/tests/early-mount-options.nix b/nixos/tests/early-mount-options.nix
new file mode 100644
index 0000000000000..8be318ae13bca
--- /dev/null
+++ b/nixos/tests/early-mount-options.nix
@@ -0,0 +1,19 @@
+# Test for https://github.com/NixOS/nixpkgs/pull/193469
+import ./make-test-python.nix {
+  name = "early-mount-options";
+
+  nodes.machine = {
+    virtualisation.fileSystems."/var" = {
+      options = [ "bind" "nosuid" "nodev" "noexec" ];
+      device = "/var";
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    var_mount_info = machine.succeed("findmnt /var -n -o OPTIONS")
+    options = var_mount_info.strip().split(",")
+    assert "nosuid" in options and "nodev" in options and "noexec" in options
+  '';
+}
diff --git a/nixos/tests/ft2-clone.nix b/nixos/tests/ft2-clone.nix
index 3c90b3d3fa201..a8395d4ebaa62 100644
--- a/nixos/tests/ft2-clone.nix
+++ b/nixos/tests/ft2-clone.nix
@@ -26,9 +26,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
 
       machine.wait_for_window(r"Fasttracker")
       machine.sleep(5)
-      # One of the few words that actually get recognized
-      if "Songlen" not in machine.get_screen_text():
-          raise Exception("Program did not start successfully")
+      machine.wait_for_text(r"(Songlen|Repstart|Time|About|Nibbles|Help)")
       machine.screenshot("screen")
     '';
 })
diff --git a/nixos/tests/geth.nix b/nixos/tests/geth.nix
index 11ad1ed2ea66f..dc6490db57c9c 100644
--- a/nixos/tests/geth.nix
+++ b/nixos/tests/geth.nix
@@ -19,6 +19,10 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         enable = true;
         port = 18545;
       };
+      authrpc = {
+        enable = true;
+        port = 18551;
+      };
     };
   };
 
@@ -31,11 +35,11 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     machine.wait_for_open_port(18545)
 
     machine.succeed(
-        'geth attach --exec eth.blockNumber http://localhost:8545 | grep \'^0$\' '
+        'geth attach --exec "eth.blockNumber" http://localhost:8545 | grep \'^0$\' '
     )
 
     machine.succeed(
-        'geth attach --exec "eth.chainId()" http://localhost:18545 | grep \'"0x5"\' '
+        'geth attach --exec "eth.blockNumber" http://localhost:18545 | grep \'^0$\' '
     )
   '';
 })
diff --git a/nixos/tests/gitea.nix b/nixos/tests/gitea.nix
index 86d4fce379291..4e6f9c79a6f92 100644
--- a/nixos/tests/gitea.nix
+++ b/nixos/tests/gitea.nix
@@ -30,7 +30,7 @@ let
 
     nodes = {
       server = { config, pkgs, ... }: {
-        virtualisation.memorySize = 2048;
+        virtualisation.memorySize = 2047;
         services.gitea = {
           enable = true;
           database = { inherit type; };
@@ -72,6 +72,7 @@ let
 
       server.wait_for_unit("gitea.service")
       server.wait_for_open_port(3000)
+      server.wait_for_open_port(22)
       server.succeed("curl --fail http://localhost:3000/")
 
       server.succeed(
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index c2a11bada0a35..672b497e7ec68 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -6,7 +6,10 @@
 # - Creating Merge Requests and merging them
 # - Opening and closing issues.
 # - Downloading repository archives as tar.gz and tar.bz2
-import ./make-test-python.nix ({ pkgs, lib, ... }:
+# Run with
+# [nixpkgs]$ nix-build -A nixosTests.gitlab
+
+{ pkgs, lib, ... }:
 
 with lib;
 
@@ -174,7 +177,7 @@ in {
         gitlab.wait_for_unit("gitlab.service")
         gitlab.wait_for_unit("gitlab-pages.service")
         gitlab.wait_for_unit("gitlab-sidekiq.service")
-        gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
+        gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
         gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
       '';
 
@@ -419,15 +422,15 @@ in {
     + ''
       gitlab.systemctl("start gitlab-backup.service")
       gitlab.wait_for_unit("gitlab-backup.service")
-      gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/backup/dump_gitlab_backup.tar")
+      gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/backup/dump_gitlab_backup.tar")
       gitlab.systemctl("stop postgresql.service gitlab.target")
       gitlab.succeed(
-          "find ${nodes.gitlab.config.services.gitlab.statePath} -mindepth 1 -maxdepth 1 -not -name backup -execdir rm -r {} +"
+          "find ${nodes.gitlab.services.gitlab.statePath} -mindepth 1 -maxdepth 1 -not -name backup -execdir rm -r {} +"
       )
       gitlab.succeed("systemd-tmpfiles --create")
-      gitlab.succeed("rm -rf ${nodes.gitlab.config.services.postgresql.dataDir}")
+      gitlab.succeed("rm -rf ${nodes.gitlab.services.postgresql.dataDir}")
       gitlab.systemctl("start gitlab-config.service gitaly.service gitlab-postgresql.service")
-      gitlab.wait_for_file("${nodes.gitlab.config.services.gitlab.statePath}/tmp/sockets/gitaly.socket")
+      gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitaly.socket")
       gitlab.succeed(
           "sudo -u gitlab -H gitlab-rake gitlab:backup:restore RAILS_ENV=production BACKUP=dump force=yes"
       )
@@ -435,4 +438,4 @@ in {
     ''
     + waitForServices
     + test false;
-})
+}
diff --git a/nixos/tests/gonic.nix b/nixos/tests/gonic.nix
new file mode 100644
index 0000000000000..726d7da0970f7
--- /dev/null
+++ b/nixos/tests/gonic.nix
@@ -0,0 +1,18 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "gonic";
+
+  nodes.machine = { ... }: {
+    services.gonic = {
+      enable = true;
+      settings = {
+        music-path = [ "/tmp" ];
+        podcast-path = "/tmp";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("gonic")
+    machine.wait_for_open_port(4747)
+  '';
+})
diff --git a/nixos/tests/harmonia.nix b/nixos/tests/harmonia.nix
new file mode 100644
index 0000000000000..6cf9ad4d23358
--- /dev/null
+++ b/nixos/tests/harmonia.nix
@@ -0,0 +1,37 @@
+{ pkgs, lib, ... }:
+
+{
+  name = "harmonia";
+
+  nodes = {
+    harmonia = {
+      services.harmonia = {
+        enable = true;
+        signKeyPath = pkgs.writeText "cache-key" "cache.example.com-1:9FhO0w+7HjZrhvmzT1VlAZw4OSAlFGTgC24Seg3tmPl4gZBdwZClzTTHr9cVzJpwsRSYLTu7hEAQe3ljy92CWg==";
+        settings.priority = 35;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 5000 ];
+      system.extraDependencies = [ pkgs.emptyFile ];
+    };
+
+    client01 = {
+      nix.settings = {
+        substituters = lib.mkForce [ "http://harmonia:5000" ];
+        trusted-public-keys = lib.mkForce [ "cache.example.com-1:eIGQXcGQpc00x6/XFcyacLEUmC07u4RAEHt5Y8vdglo=" ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    start_all()
+
+    harmonia.wait_for_unit("harmonia.service")
+
+    client01.wait_until_succeeds("curl -f http://harmonia:5000/nix-cache-info | grep '${toString nodes.harmonia.services.harmonia.settings.priority}' >&2")
+    client01.succeed("curl -f http://harmonia:5000/version | grep '${nodes.harmonia.services.harmonia.package.version}' >&2")
+
+    client01.succeed("cat /etc/nix/nix.conf >&2")
+    client01.succeed("nix-store --realise ${pkgs.emptyFile} --store /root/other-store")
+  '';
+}
diff --git a/nixos/tests/headscale.nix b/nixos/tests/headscale.nix
index 48658b5dade42..d3e861c73008d 100644
--- a/nixos/tests/headscale.nix
+++ b/nixos/tests/headscale.nix
@@ -12,6 +12,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     machine.wait_for_open_port(8080)
     # Test basic funcionality
     machine.succeed("headscale namespaces create test")
-    machine.succeed("headscale preauthkeys -n test create")
+    machine.succeed("headscale preauthkeys -u test create")
   '';
 })
diff --git a/nixos/tests/hibernate.nix b/nixos/tests/hibernate.nix
index cb75322ca5f92..4d0b53e95b32a 100644
--- a/nixos/tests/hibernate.nix
+++ b/nixos/tests/hibernate.nix
@@ -63,7 +63,7 @@ in makeTest {
         # Small root disk for installer
         512
       ];
-      virtualisation.bootDevice = "/dev/vdb";
+      virtualisation.rootDevice = "/dev/vdb";
     };
   };
 
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 8585cb3585fef..22a63cc736640 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -61,6 +61,13 @@ in {
         # https://www.home-assistant.io/integrations/frontend/
         frontend = {};
 
+        # include some popular integrations, that absolutely shouldn't break
+        esphome = {};
+        knx = {};
+        matter = {};
+        shelly = {};
+        zha = {};
+
         # set up a wake-on-lan switch to test capset capability required
         # for the ping suid wrapper
         # https://www.home-assistant.io/integrations/wake_on_lan/
@@ -107,7 +114,7 @@ in {
     # Cause a configuration change that requires a service restart as we added a new runtime dependency
     specialisation.newFeature = {
       inheritParentConfig = true;
-      configuration.services.home-assistant.config.esphome = {};
+      configuration.services.home-assistant.config.backup = {};
     };
   };
 
diff --git a/nixos/tests/initrd-luks-empty-passphrase.nix b/nixos/tests/initrd-luks-empty-passphrase.nix
index 41765a395ec65..d2805f2f17342 100644
--- a/nixos/tests/initrd-luks-empty-passphrase.nix
+++ b/nixos/tests/initrd-luks-empty-passphrase.nix
@@ -30,26 +30,26 @@ in {
     specialisation.boot-luks-wrong-keyfile.configuration = {
       boot.initrd.luks.devices = lib.mkVMOverride {
         cryptroot = {
-          device = "/dev/vdc";
+          device = "/dev/vdb";
           keyFile = "/etc/cryptroot.key";
           tryEmptyPassphrase = true;
           fallbackToPassword = !systemdStage1;
         };
       };
-      virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
       boot.initrd.secrets."/etc/cryptroot.key" = keyfile;
     };
 
     specialisation.boot-luks-missing-keyfile.configuration = {
       boot.initrd.luks.devices = lib.mkVMOverride {
         cryptroot = {
-          device = "/dev/vdc";
+          device = "/dev/vdb";
           keyFile = "/etc/cryptroot.key";
           tryEmptyPassphrase = true;
           fallbackToPassword = !systemdStage1;
         };
       };
-      virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
     };
   };
 
@@ -76,7 +76,7 @@ in {
 
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
-    machine.succeed("echo "" | cryptsetup luksFormat /dev/vdc --batch-mode")
+    machine.succeed("echo "" | cryptsetup luksFormat /dev/vdb --batch-mode")
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks-wrong-keyfile.conf")
     machine.succeed("sync")
     machine.crash()
diff --git a/nixos/tests/initrd-network-openvpn/default.nix b/nixos/tests/initrd-network-openvpn/default.nix
index dbb34c28eea74..769049905eb8c 100644
--- a/nixos/tests/initrd-network-openvpn/default.nix
+++ b/nixos/tests/initrd-network-openvpn/default.nix
@@ -1,3 +1,9 @@
+{ system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
+, systemdStage1 ? false
+}:
+
 import ../make-test-python.nix ({ lib, ...}:
 
 {
@@ -22,11 +28,12 @@ import ../make-test-python.nix ({ lib, ...}:
       minimalboot =
         { ... }:
         {
+          boot.initrd.systemd.enable = systemdStage1;
           boot.initrd.network = {
             enable = true;
             openvpn = {
               enable = true;
-              configuration = "/dev/null";
+              configuration = builtins.toFile "initrd.ovpn" "";
             };
           };
         };
@@ -39,6 +46,17 @@ import ../make-test-python.nix ({ lib, ...}:
           virtualisation.vlans = [ 1 ];
 
           boot.initrd = {
+            systemd.enable = systemdStage1;
+            systemd.extraBin.nc = "${pkgs.busybox}/bin/nc";
+            systemd.services.nc = {
+              requiredBy = ["initrd.target"];
+              after = ["network.target"];
+              serviceConfig = {
+                ExecStart = "/bin/nc -p 1234 -lke /bin/echo TESTVALUE";
+                Type = "oneshot";
+              };
+            };
+
             # This command does not fork to keep the VM in the state where
             # only the initramfs is loaded
             preLVMCommands =
diff --git a/nixos/tests/initrd-network-ssh/default.nix b/nixos/tests/initrd-network-ssh/default.nix
index 0ad0563b0ce15..017de6882081d 100644
--- a/nixos/tests/initrd-network-ssh/default.nix
+++ b/nixos/tests/initrd-network-ssh/default.nix
@@ -22,10 +22,6 @@ import ../make-test-python.nix ({ lib, ... }:
             hostKeys = [ ./ssh_host_ed25519_key ];
           };
         };
-        boot.initrd.extraUtilsCommands = ''
-          mkdir -p $out/secrets/etc/ssh
-          cat "${./ssh_host_ed25519_key}" > $out/secrets/etc/ssh/sh_host_ed25519_key
-        '';
         boot.initrd.preLVMCommands = ''
           while true; do
             if [ -f fnord ]; then
diff --git a/nixos/tests/initrd-secrets-changing.nix b/nixos/tests/initrd-secrets-changing.nix
index 775c69d0142db..d6f9ef9ced83a 100644
--- a/nixos/tests/initrd-secrets-changing.nix
+++ b/nixos/tests/initrd-secrets-changing.nix
@@ -15,7 +15,6 @@ testing.makeTest {
 
   nodes.machine = { ... }: {
     virtualisation.useBootLoader = true;
-    virtualisation.persistBootDevice = true;
 
     boot.loader.grub.device = "/dev/vda";
 
diff --git a/nixos/tests/installed-tests/pipewire.nix b/nixos/tests/installed-tests/pipewire.nix
index b04265658fcf4..6e69ada8612fd 100644
--- a/nixos/tests/installed-tests/pipewire.nix
+++ b/nixos/tests/installed-tests/pipewire.nix
@@ -1,15 +1,5 @@
-{ pkgs, lib, makeInstalledTest, ... }:
+{ pkgs, makeInstalledTest, ... }:
 
 makeInstalledTest {
   tested = pkgs.pipewire;
-  testConfig = {
-    hardware.pulseaudio.enable = false;
-    services.pipewire = {
-      enable = true;
-      pulse.enable = true;
-      jack.enable = true;
-      alsa.enable = true;
-      alsa.support32Bit = true;
-    };
-  };
 }
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index d441765fe194d..28b93472263f4 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -10,7 +10,7 @@ with pkgs.lib;
 let
 
   # The configuration to install.
-  makeConfig = { bootLoader, grubVersion, grubDevice, grubIdentifier, grubUseEfi
+  makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi
                , extraConfig, forceGrubReinstallCount ? 0
                }:
     pkgs.writeText "configuration.nix" ''
@@ -29,11 +29,6 @@ let
         ${optionalString systemdStage1 "boot.initrd.systemd.enable = true;"}
 
         ${optionalString (bootLoader == "grub") ''
-          boot.loader.grub.version = ${toString grubVersion};
-          ${optionalString (grubVersion == 1) ''
-            boot.loader.grub.splashImage = null;
-          ''}
-
           boot.loader.grub.extraConfig = "serial; terminal_output serial";
           ${if grubUseEfi then ''
             boot.loader.grub.device = "nodev";
@@ -70,11 +65,11 @@ let
   # disk, and then reboot from the hard disk.  It's parameterized with
   # a test script fragment `createPartitions', which must create
   # partitions and filesystems.
-  testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
+  testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi
                   , grubIdentifier, preBootCommands, postBootCommands, extraConfig
                   , testSpecialisationConfig
                   }:
-    let iface = if grubVersion == 1 then "ide" else "virtio";
+    let iface = "virtio";
         isEfi = bootLoader == "systemd-boot" || (bootLoader == "grub" && grubUseEfi);
         bios  = if pkgs.stdenv.isAarch64 then "QEMU_EFI.fd" else "OVMF.fd";
     in if !isEfi && !pkgs.stdenv.hostPlatform.isx86 then ''
@@ -122,7 +117,7 @@ let
           machine.succeed("cat /mnt/etc/nixos/hardware-configuration.nix >&2")
           machine.copy_from_host(
               "${ makeConfig {
-                    inherit bootLoader grubVersion grubDevice grubIdentifier
+                    inherit bootLoader grubDevice grubIdentifier
                             grubUseEfi extraConfig;
                   }
               }",
@@ -193,7 +188,7 @@ let
           # doesn't know about the host-guest sharing mechanism.
           machine.copy_from_host_via_shell(
               "${ makeConfig {
-                    inherit bootLoader grubVersion grubDevice grubIdentifier
+                    inherit bootLoader grubDevice grubIdentifier
                             grubUseEfi extraConfig;
                     forceGrubReinstallCount = 1;
                   }
@@ -222,7 +217,7 @@ let
       # doesn't know about the host-guest sharing mechanism.
       machine.copy_from_host_via_shell(
           "${ makeConfig {
-                inherit bootLoader grubVersion grubDevice grubIdentifier
+                inherit bootLoader grubDevice grubIdentifier
                 grubUseEfi extraConfig;
                 forceGrubReinstallCount = 2;
               }
@@ -284,7 +279,7 @@ let
     { createPartitions, preBootCommands ? "", postBootCommands ? "", extraConfig ? ""
     , extraInstallerConfig ? {}
     , bootLoader ? "grub" # either "grub" or "systemd-boot"
-    , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
+    , grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
     , enableOCR ? false, meta ? {}
     , testSpecialisationConfig ? false
     }:
@@ -316,10 +311,9 @@ let
           # installer. This ensures the target disk (/dev/vda) is
           # the same during and after installation.
           virtualisation.emptyDiskImages = [ 512 ];
-          virtualisation.bootDevice =
-            if grubVersion == 1 then "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive2" else "/dev/vdb";
-          virtualisation.qemu.diskInterface =
-            if grubVersion == 1 then "scsi" else "virtio";
+          virtualisation.rootDevice = "/dev/vdb";
+          virtualisation.bootLoaderDevice = "/dev/vda";
+          virtualisation.qemu.diskInterface = "virtio";
 
           # We don't want to have any networking in the guest whatsoever.
           # Also, if any vlans are enabled, the guest will reboot
@@ -344,6 +338,7 @@ let
             (docbook-xsl-ns.override {
               withManOptDedupPatch = true;
             })
+            kbd.dev
             kmod.dev
             libarchive.dev
             libxml2.bin
@@ -370,8 +365,7 @@ let
             # curl's tarball, we see what it's trying to download
             curl
           ]
-          ++ optional (bootLoader == "grub" && grubVersion == 1) pkgs.grub
-          ++ optionals (bootLoader == "grub" && grubVersion == 2) (let
+          ++ optionals (bootLoader == "grub") (let
             zfsSupport = lib.any (x: x == "zfs")
               (extraInstallerConfig.boot.supportedFilesystems or []);
           in [
@@ -390,7 +384,7 @@ let
 
       testScript = testScriptFun {
         inherit bootLoader createPartitions preBootCommands postBootCommands
-                grubVersion grubDevice grubIdentifier grubUseEfi extraConfig
+                grubDevice grubIdentifier grubUseEfi extraConfig
                 testSpecialisationConfig;
       };
     };
@@ -873,26 +867,6 @@ in {
     '';
   };
 
-  # Test a basic install using GRUB 1.
-  grub1 = makeInstallerTest "grub1" rec {
-    createPartitions = ''
-      machine.succeed(
-          "flock ${grubDevice} parted --script ${grubDevice} -- mklabel msdos"
-          + " mkpart primary linux-swap 1M 1024M"
-          + " mkpart primary ext2 1024M -1s",
-          "udevadm settle",
-          "mkswap ${grubDevice}-part1 -L swap",
-          "swapon -L swap",
-          "mkfs.ext3 -L nixos ${grubDevice}-part2",
-          "mount LABEL=nixos /mnt",
-          "mkdir -p /mnt/tmp",
-      )
-    '';
-    grubVersion = 1;
-    # /dev/sda is not stable, even when the SCSI disk number is.
-    grubDevice = "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive1";
-  };
-
   # Test using labels to identify volumes in grub
   simpleLabels = makeInstallerTest "simpleLabels" {
     createPartitions = ''
diff --git a/nixos/tests/kanidm.nix b/nixos/tests/kanidm.nix
index 33c65026b9b1a..d9c0542c4c2e0 100644
--- a/nixos/tests/kanidm.nix
+++ b/nixos/tests/kanidm.nix
@@ -44,7 +44,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
         };
       };
 
-      networking.hosts."${nodes.server.config.networking.primaryIPAddress}" = [ serverDomain ];
+      networking.hosts."${nodes.server.networking.primaryIPAddress}" = [ serverDomain ];
 
       security.pki.certificateFiles = [ certs.ca.cert ];
     };
@@ -56,7 +56,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
         # We need access to the config file in the test script.
         filteredConfig = pkgs.lib.converge
           (pkgs.lib.filterAttrsRecursive (_: v: v != null))
-          nodes.server.config.services.kanidm.serverSettings;
+          nodes.server.services.kanidm.serverSettings;
         serverConfigFile = (pkgs.formats.toml { }).generate "server.toml" filteredConfig;
 
       in
diff --git a/nixos/tests/kavita.nix b/nixos/tests/kavita.nix
new file mode 100644
index 0000000000000..f27b3fffbcf64
--- /dev/null
+++ b/nixos/tests/kavita.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "kavita";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ misterio77 ];
+  };
+
+  nodes = {
+    kavita = { config, pkgs, ... }: {
+      services.kavita = {
+        enable = true;
+        port = 5000;
+        tokenKeyFile = builtins.toFile "kavita.key" "QfpjFvjT83BLtZ74GE3U3Q==";
+      };
+    };
+  };
+
+  testScript = let
+    regUrl = "http://kavita:5000/api/Account/register";
+    payload = builtins.toFile "payload.json" (builtins.toJSON {
+      username = "foo";
+      password = "correcthorsebatterystaple";
+      email = "foo@bar";
+    });
+  in ''
+    kavita.start
+    kavita.wait_for_unit("kavita.service")
+
+    # Check that static assets are working
+    kavita.wait_until_succeeds("curl http://kavita:5000/site.webmanifest | grep Kavita")
+
+    # Check that registration is working
+    kavita.succeed("curl -fX POST ${regUrl} --json @${payload}")
+    # But only for the first one
+    kavita.fail("curl -fX POST ${regUrl} --json @${payload}")
+  '';
+})
diff --git a/nixos/tests/lldap.nix b/nixos/tests/lldap.nix
new file mode 100644
index 0000000000000..d6c3a865aa04a
--- /dev/null
+++ b/nixos/tests/lldap.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix ({ ... }: {
+  name = "lldap";
+
+  nodes.machine = { pkgs, ... }: {
+    services.lldap = {
+      enable = true;
+      settings = {
+        verbose = true;
+        ldap_base_dn = "dc=example,dc=com";
+      };
+    };
+    environment.systemPackages = [ pkgs.openldap ];
+  };
+
+  testScript = ''
+    machine.wait_for_unit("lldap.service")
+    machine.wait_for_open_port(3890)
+    machine.wait_for_open_port(17170)
+
+    machine.succeed("curl --location --fail http://localhost:17170/")
+
+    print(
+      machine.succeed('ldapsearch -H ldap://localhost:3890 -D uid=admin,ou=people,dc=example,dc=com -b "ou=people,dc=example,dc=com" -w password')
+    )
+  '';
+})
diff --git a/nixos/tests/luks.nix b/nixos/tests/luks.nix
index 82f5095cb2602..c2b95c6a95fbd 100644
--- a/nixos/tests/luks.nix
+++ b/nixos/tests/luks.nix
@@ -18,10 +18,10 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       boot-luks.configuration = {
         boot.initrd.luks.devices = lib.mkVMOverride {
           # We have two disks and only type one password - key reuse is in place
-          cryptroot.device = "/dev/vdc";
-          cryptroot2.device = "/dev/vdd";
+          cryptroot.device = "/dev/vdb";
+          cryptroot2.device = "/dev/vdc";
         };
-        virtualisation.bootDevice = "/dev/mapper/cryptroot";
+        virtualisation.rootDevice = "/dev/mapper/cryptroot";
       };
       boot-luks-custom-keymap.configuration = lib.mkMerge [
         boot-luks.configuration
@@ -37,8 +37,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   testScript = ''
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
     machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
-    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdd -")
 
     # Boot from the encrypted disk
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
diff --git a/nixos/tests/lvm2/systemd-stage-1.nix b/nixos/tests/lvm2/systemd-stage-1.nix
index 617ba77b1796c..b711cd22d7f42 100644
--- a/nixos/tests/lvm2/systemd-stage-1.nix
+++ b/nixos/tests/lvm2/systemd-stage-1.nix
@@ -1,18 +1,18 @@
 { kernelPackages ? null, flavour }: let
   preparationCode = {
     raid = ''
-      machine.succeed("vgcreate test_vg /dev/vdc /dev/vdd")
+      machine.succeed("vgcreate test_vg /dev/vdb /dev/vdc")
       machine.succeed("lvcreate -L 512M --type raid0 test_vg -n test_lv")
     '';
 
     thinpool = ''
-      machine.succeed("vgcreate test_vg /dev/vdc")
+      machine.succeed("vgcreate test_vg /dev/vdb")
       machine.succeed("lvcreate -L 512M -T test_vg/test_thin_pool")
       machine.succeed("lvcreate -n test_lv -V 16G --thinpool test_thin_pool test_vg")
     '';
 
     vdo = ''
-      machine.succeed("vgcreate test_vg /dev/vdc")
+      machine.succeed("vgcreate test_vg /dev/vdb")
       machine.succeed("lvcreate --type vdo -n test_lv -L 6G -V 12G test_vg/vdo_pool_lv")
     '';
   }.${flavour};
@@ -79,7 +79,7 @@ in import ../make-test-python.nix ({ pkgs, ... }: {
       kernelPackages = lib.mkIf (kernelPackages != null) kernelPackages;
     };
 
-    specialisation.boot-lvm.configuration.virtualisation.bootDevice = "/dev/test_vg/test_lv";
+    specialisation.boot-lvm.configuration.virtualisation.rootDevice = "/dev/test_vg/test_lv";
   };
 
   testScript = ''
diff --git a/nixos/tests/maddy/default.nix b/nixos/tests/maddy/default.nix
new file mode 100644
index 0000000000000..043906863e646
--- /dev/null
+++ b/nixos/tests/maddy/default.nix
@@ -0,0 +1,6 @@
+{ handleTest }:
+
+{
+  unencrypted = handleTest ./unencrypted.nix { };
+  tls = handleTest ./tls.nix { };
+}
diff --git a/nixos/tests/maddy/tls.nix b/nixos/tests/maddy/tls.nix
new file mode 100644
index 0000000000000..44da4cf2a3cf2
--- /dev/null
+++ b/nixos/tests/maddy/tls.nix
@@ -0,0 +1,94 @@
+import ../make-test-python.nix ({ pkgs, ... }:
+let
+  certs = import ../common/acme/server/snakeoil-certs.nix;
+  domain = certs.domain;
+in {
+  name = "maddy-tls";
+  meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
+
+  nodes = {
+    server = { options, ... }: {
+      services.maddy = {
+        enable = true;
+        hostname = domain;
+        primaryDomain = domain;
+        openFirewall = true;
+        ensureAccounts = [ "postmaster@${domain}" ];
+        ensureCredentials = {
+          # Do not use this in production. This will make passwords world-readable
+          # in the Nix store
+          "postmaster@${domain}".passwordFile = "${pkgs.writeText "postmaster" "test"}";
+        };
+        tls = {
+          loader = "file";
+          certificates = [{
+            certPath = "${certs.${domain}.cert}";
+            keyPath = "${certs.${domain}.key}";
+          }];
+        };
+        # Enable TLS listeners. Configuring this via the module is not yet
+        # implemented.
+        config = builtins.replaceStrings [
+          "imap tcp://0.0.0.0:143"
+          "submission tcp://0.0.0.0:587"
+        ] [
+          "imap tls://0.0.0.0:993 tcp://0.0.0.0:143"
+          "submission tls://0.0.0.0:465 tcp://0.0.0.0:587"
+        ] options.services.maddy.config.default;
+      };
+      # Not covered by openFirewall yet
+      networking.firewall.allowedTCPPorts = [ 993 465 ];
+    };
+
+    client = { nodes, ... }: {
+      security.pki.certificateFiles = [
+        certs.ca.cert
+      ];
+      networking.extraHosts = ''
+        ${nodes.server.networking.primaryIPAddress} ${domain}
+     '';
+      environment.systemPackages = [
+        (pkgs.writers.writePython3Bin "send-testmail" { } ''
+          import smtplib
+          import ssl
+          from email.mime.text import MIMEText
+
+          context = ssl.create_default_context()
+          msg = MIMEText("Hello World")
+          msg['Subject'] = 'Test'
+          msg['From'] = "postmaster@${domain}"
+          msg['To'] = "postmaster@${domain}"
+          with smtplib.SMTP_SSL(host='${domain}', port=465, context=context) as smtp:
+              smtp.login('postmaster@${domain}', 'test')
+              smtp.sendmail(
+                'postmaster@${domain}', 'postmaster@${domain}', msg.as_string()
+              )
+        '')
+        (pkgs.writers.writePython3Bin "test-imap" { } ''
+          import imaplib
+
+          with imaplib.IMAP4_SSL('${domain}') as imap:
+              imap.login('postmaster@${domain}', 'test')
+              imap.select()
+              status, refs = imap.search(None, 'ALL')
+              assert status == 'OK'
+              assert len(refs) == 1
+              status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+              assert status == 'OK'
+              assert msg[0][1].strip() == b"Hello World"
+        '')
+      ];
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("maddy.service")
+    server.wait_for_open_port(143)
+    server.wait_for_open_port(993)
+    server.wait_for_open_port(587)
+    server.wait_for_open_port(465)
+    client.succeed("send-testmail")
+    client.succeed("test-imap")
+  '';
+})
diff --git a/nixos/tests/maddy.nix b/nixos/tests/maddy/unencrypted.nix
index 7420430333372..2420d461e4e7c 100644
--- a/nixos/tests/maddy.nix
+++ b/nixos/tests/maddy/unencrypted.nix
@@ -1,5 +1,5 @@
-import ./make-test-python.nix ({ pkgs, ... }: {
-  name = "maddy";
+import ../make-test-python.nix ({ pkgs, ... }: {
+  name = "maddy-unencrypted";
   meta = with pkgs.lib.maintainers; { maintainers = [ onny ]; };
 
   nodes = {
diff --git a/nixos/tests/matrix/mjolnir.nix b/nixos/tests/matrix/mjolnir.nix
index c88113cb260b7..8a888b17a3d7c 100644
--- a/nixos/tests/matrix/mjolnir.nix
+++ b/nixos/tests/matrix/mjolnir.nix
@@ -32,7 +32,6 @@ import ../make-test-python.nix (
     name = "mjolnir";
     meta = with pkgs.lib; {
       maintainers = teams.matrix.members;
-      broken = true; # times out after spending many hours
     };
 
     nodes = {
@@ -99,6 +98,8 @@ import ../make-test-python.nix (
             enable = true;
             username = "mjolnir";
             passwordFile = pkgs.writeText "password.txt" "mjolnir-password";
+            # otherwise mjolnir tries to connect to ::1, which is not listened by pantalaimon
+            options.listenAddress = "127.0.0.1";
           };
           managementRoom = "#moderators:homeserver";
         };
diff --git a/nixos/tests/mediawiki.nix b/nixos/tests/mediawiki.nix
index 1ae82d65b3cb9..52122755ad947 100644
--- a/nixos/tests/mediawiki.nix
+++ b/nixos/tests/mediawiki.nix
@@ -7,8 +7,8 @@
 let
   shared = {
     services.mediawiki.enable = true;
-    services.mediawiki.virtualHost.hostName = "localhost";
-    services.mediawiki.virtualHost.adminAddr = "root@example.com";
+    services.mediawiki.httpd.virtualHost.hostName = "localhost";
+    services.mediawiki.httpd.virtualHost.adminAddr = "root@example.com";
     services.mediawiki.passwordFile = pkgs.writeText "password" "correcthorsebatterystaple";
     services.mediawiki.extensions = {
       Matomo = pkgs.fetchzip {
@@ -54,4 +54,24 @@ in
       assert "MediaWiki has been installed" in page
     '';
   };
+
+  nohttpd = testLib.makeTest {
+    name = "mediawiki-nohttpd";
+    nodes.machine = {
+      services.mediawiki.webserver = "none";
+    };
+    testScript = { nodes, ... }: ''
+      start_all()
+      machine.wait_for_unit("phpfpm-mediawiki.service")
+      env = (
+        "SCRIPT_NAME=/index.php",
+        "SCRIPT_FILENAME=${nodes.machine.services.mediawiki.finalPackage}/share/mediawiki/index.php",
+        "REMOTE_ADDR=127.0.0.1",
+        'QUERY_STRING=title=Main_Page',
+        "REQUEST_METHOD=GET",
+      );
+      page = machine.succeed(f"{' '.join(env)} ${pkgs.fcgi}/bin/cgi-fcgi -bind -connect ${nodes.machine.services.phpfpm.pools.mediawiki.socket}")
+      assert "MediaWiki has been installed" in page, f"no 'MediaWiki has been installed' in:\n{page}"
+    '';
+  };
 }
diff --git a/nixos/tests/mosquitto.nix b/nixos/tests/mosquitto.nix
index 70eecc89278b3..8eca4f2592251 100644
--- a/nixos/tests/mosquitto.nix
+++ b/nixos/tests/mosquitto.nix
@@ -66,6 +66,7 @@ in {
   in {
     server = { pkgs, ... }: {
       networking.firewall.allowedTCPPorts = [ port tlsPort anonPort ];
+      networking.useNetworkd = true;
       services.mosquitto = {
         enable = true;
         settings = {
diff --git a/nixos/tests/mysql/mysql-replication.nix b/nixos/tests/mysql/mysql-replication.nix
index f6014019bd53a..8f1695eb97e27 100644
--- a/nixos/tests/mysql/mysql-replication.nix
+++ b/nixos/tests/mysql/mysql-replication.nix
@@ -42,7 +42,7 @@ let
           enable = true;
           replication.role = "slave";
           replication.serverId = 2;
-          replication.masterHost = nodes.primary.config.networking.hostName;
+          replication.masterHost = nodes.primary.networking.hostName;
           replication.masterUser = replicateUser;
           replication.masterPassword = replicatePassword;
         };
@@ -54,7 +54,7 @@ let
           enable = true;
           replication.role = "slave";
           replication.serverId = 3;
-          replication.masterHost = nodes.primary.config.networking.hostName;
+          replication.masterHost = nodes.primary.networking.hostName;
           replication.masterUser = replicateUser;
           replication.masterPassword = replicatePassword;
         };
diff --git a/nixos/tests/mysql/mysql.nix b/nixos/tests/mysql/mysql.nix
index 197e6da80e24d..6ddc49f86f7c0 100644
--- a/nixos/tests/mysql/mysql.nix
+++ b/nixos/tests/mysql/mysql.nix
@@ -15,7 +15,7 @@ let
     name ? mkTestName package,
     useSocketAuth ? true,
     hasMroonga ? true,
-    hasRocksDB ? true
+    hasRocksDB ? pkgs.stdenv.hostPlatform.is64bit
   }: makeTest {
     inherit name;
     meta = with lib.maintainers; {
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
index a475049e7b264..e17f701c54b7d 100644
--- a/nixos/tests/nextcloud/basic.nix
+++ b/nixos/tests/nextcloud/basic.nix
@@ -43,6 +43,7 @@ in {
         enable = true;
         datadir = "/var/lib/nextcloud-data";
         hostName = "nextcloud";
+        database.createLocally = true;
         config = {
           # Don't inherit adminuser since "root" is supposed to be the default
           adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home!
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
index 350486e8c733c..78fe026b4a84c 100644
--- a/nixos/tests/nextcloud/default.nix
+++ b/nixos/tests/nextcloud/default.nix
@@ -26,4 +26,4 @@ foldl
     };
   })
 { }
-  [ 24 25 26 ]
+  [ 25 26 ]
diff --git a/nixos/tests/nextcloud/openssl-sse.nix b/nixos/tests/nextcloud/openssl-sse.nix
index 871947e1d2b20..e1f2706a7348b 100644
--- a/nixos/tests/nextcloud/openssl-sse.nix
+++ b/nixos/tests/nextcloud/openssl-sse.nix
@@ -9,6 +9,7 @@ args@{ pkgs, nextcloudVersion ? 25, ... }:
     services.nextcloud = {
       enable = true;
       config.adminpassFile = "${pkgs.writeText "adminpass" adminpass}";
+      database.createLocally = true;
       package = pkgs.${"nextcloud" + (toString nextcloudVersion)};
     };
   };
diff --git a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
index 93e655c3056be..ce0019e9da4a7 100644
--- a/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
+++ b/nixos/tests/nextcloud/with-declarative-redis-and-secrets.nix
@@ -1,6 +1,11 @@
 import ../make-test-python.nix ({ pkgs, ...}: let
-  adminpass = "hunter2";
-  adminuser = "custom-admin-username";
+  username = "custom_admin_username";
+  # This will be used both for redis and postgresql
+  pass = "hunter2";
+  # Don't do this at home, use a file outside of the nix store instead
+  passFile = toString (pkgs.writeText "pass-file" ''
+    ${pass}
+  '');
 in {
   name = "nextcloud-with-declarative-redis";
   meta = with pkgs.lib.maintainers; {
@@ -22,15 +27,15 @@ in {
           redis = true;
           memcached = false;
         };
+        # This test also validates that we can use an "external" database
+        database.createLocally = false;
         config = {
           dbtype = "pgsql";
           dbname = "nextcloud";
-          dbuser = "nextcloud";
-          dbhost = "/run/postgresql";
-          inherit adminuser;
-          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
-            ${adminpass}
-          '');
+          dbuser = username;
+          dbpassFile = passFile;
+          adminuser = username;
+          adminpassFile = passFile;
         };
         secretFile = "/etc/nextcloud-secrets.json";
 
@@ -52,20 +57,20 @@ in {
 
       systemd.services.nextcloud-setup= {
         requires = ["postgresql.service"];
-        after = [
-          "postgresql.service"
-        ];
+        after = [ "postgresql.service" ];
       };
 
       services.postgresql = {
         enable = true;
-        ensureDatabases = [ "nextcloud" ];
-        ensureUsers = [
-          { name = "nextcloud";
-            ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
-          }
-        ];
       };
+      systemd.services.postgresql.postStart = pkgs.lib.mkAfter ''
+        password=$(cat ${passFile})
+        ${config.services.postgresql.package}/bin/psql <<EOF
+          CREATE ROLE ${username} WITH LOGIN PASSWORD '$password' CREATEDB;
+          CREATE DATABASE nextcloud;
+          GRANT ALL PRIVILEGES ON DATABASE nextcloud TO ${username};
+        EOF
+      '';
 
       # This file is meant to contain secret options which should
       # not go into the nix store. Here it is just used to set the
@@ -86,8 +91,8 @@ in {
       export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
       export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
       export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
-      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
-      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${username}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${pass})"
       "''${@}"
     '';
     copySharedFile = pkgs.writeScript "copy-shared-file" ''
diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
index 63e0e2c59639e..e57aabfaf86b5 100644
--- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix
+++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -29,21 +29,11 @@ in {
         database.createLocally = true;
         config = {
           dbtype = "mysql";
-          dbname = "nextcloud";
-          dbuser = "nextcloud";
-          dbhost = "127.0.0.1";
-          dbport = 3306;
-          dbpassFile = "${pkgs.writeText "dbpass" "hunter2" }";
           # Don't inherit adminuser since "root" is supposed to be the default
           adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home!
         };
       };
 
-      systemd.services.nextcloud-setup= {
-        requires = ["mysql.service"];
-        after = ["mysql.service"];
-      };
-
       services.memcached.enable = true;
     };
   };
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index d28c1bdfd6e1e..1cbb131042876 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -25,11 +25,9 @@ in {
           redis = true;
           memcached = false;
         };
+        database.createLocally = true;
         config = {
           dbtype = "pgsql";
-          dbname = "nextcloud";
-          dbuser = "nextcloud";
-          dbhost = "/run/postgresql";
           inherit adminuser;
           adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
             ${adminpass}
@@ -48,23 +46,6 @@ in {
 
       services.redis.servers."nextcloud".enable = true;
       services.redis.servers."nextcloud".port = 6379;
-
-      systemd.services.nextcloud-setup= {
-        requires = ["postgresql.service"];
-        after = [
-          "postgresql.service"
-        ];
-      };
-
-      services.postgresql = {
-        enable = true;
-        ensureDatabases = [ "nextcloud" ];
-        ensureUsers = [
-          { name = "nextcloud";
-            ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
-          }
-        ];
-      };
     };
   };
 
diff --git a/nixos/tests/nfs/simple.nix b/nixos/tests/nfs/simple.nix
index 1e319a8eec810..026da9563bc03 100644
--- a/nixos/tests/nfs/simple.nix
+++ b/nixos/tests/nfs/simple.nix
@@ -89,6 +89,7 @@ in
           t1 = time.monotonic()
           client1.shutdown()
           duration = time.monotonic() - t1
-          assert duration < 30, f"shutdown took too long ({duration} seconds)"
+          # FIXME: regressed in kernel 6.1.28, temporarily disabled while investigating
+          # assert duration < 30, f"shutdown took too long ({duration} seconds)"
     '';
 })
diff --git a/nixos/tests/nginx.nix b/nixos/tests/nginx.nix
index 2a7e0f48d868b..8b1f921ec5209 100644
--- a/nixos/tests/nginx.nix
+++ b/nixos/tests/nginx.nix
@@ -87,15 +87,23 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         return etag
 
 
-    webserver.wait_for_unit("nginx")
-    webserver.wait_for_open_port(80)
+    def wait_for_nginx_on_port(port):
+        webserver.wait_for_unit("nginx")
+        webserver.wait_for_open_port(port)
+
+
+    # nginx can be ready before multi-user.target, in which case switching to
+    # a different configuration might not realize it needs to restart nginx.
+    webserver.wait_for_unit("multi-user.target")
+
+    wait_for_nginx_on_port(80)
 
     with subtest("check ETag if serving Nix store paths"):
         old_etag = check_etag()
         webserver.succeed(
             "${etagSystem}/bin/switch-to-configuration test >&2"
         )
-        webserver.sleep(1)
+        wait_for_nginx_on_port(80)
         new_etag = check_etag()
         assert old_etag != new_etag
 
@@ -103,7 +111,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         webserver.succeed(
             "${justReloadSystem}/bin/switch-to-configuration test >&2"
         )
-        webserver.wait_for_open_port(8080)
+        wait_for_nginx_on_port(8080)
         webserver.fail("journalctl -u nginx | grep -q -i stopped")
         webserver.succeed("journalctl -u nginx | grep -q -i reloaded")
 
@@ -111,7 +119,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         webserver.succeed(
             "${reloadRestartSystem}/bin/switch-to-configuration test >&2"
         )
-        webserver.wait_for_unit("nginx")
+        wait_for_nginx_on_port(80)
         webserver.succeed("journalctl -u nginx | grep -q -i stopped")
 
     with subtest("nixos-rebuild --switch should fail when there are configuration errors"):
diff --git a/nixos/tests/extra-python-packages.nix b/nixos/tests/nixos-test-driver/extra-python-packages.nix
index 7a48077cf98bc..1146bedd996f8 100644
--- a/nixos/tests/extra-python-packages.nix
+++ b/nixos/tests/nixos-test-driver/extra-python-packages.nix
@@ -1,4 +1,4 @@
-import ./make-test-python.nix ({ ... }:
+import ../make-test-python.nix ({ ... }:
   {
     name = "extra-python-packages";
 
diff --git a/nixos/tests/nixos-test-driver/node-name.nix b/nixos/tests/nixos-test-driver/node-name.nix
new file mode 100644
index 0000000000000..31386813a516a
--- /dev/null
+++ b/nixos/tests/nixos-test-driver/node-name.nix
@@ -0,0 +1,33 @@
+{
+  name = "nixos-test-driver.node-name";
+  nodes = {
+    "ok" = { };
+
+    # Valid node name, but not a great host name.
+    "one_two" = { };
+
+    # Valid node name, good host name
+    "a-b" = { };
+
+    # TODO: would be nice to test these eval failures
+    # Not allowed by lib/testing/network.nix (yet?)
+    # "foo.bar" = { };
+    # Not allowed.
+    # "not ok" = { }; # not ok
+  };
+
+  testScript = ''
+    start_all()
+
+    with subtest("python vars exist and machines are reachable through test backdoor"):
+      ok.succeed("true")
+      one_two.succeed("true")
+      a_b.succeed("true")
+
+    with subtest("hostname is derived from the node name"):
+      ok.succeed("hostname | tee /dev/stderr | grep '^ok$'")
+      one_two.succeed("hostname | tee /dev/stderr | grep '^onetwo$'")
+      a_b.succeed("hostname | tee /dev/stderr | grep '^a-b$'")
+
+  '';
+}
diff --git a/nixos/tests/non-default-filesystems.nix b/nixos/tests/non-default-filesystems.nix
index 7fa75aaad724d..03cc5bf709a48 100644
--- a/nixos/tests/non-default-filesystems.nix
+++ b/nixos/tests/non-default-filesystems.nix
@@ -1,54 +1,106 @@
-import ./make-test-python.nix ({ lib, pkgs, ... }:
-{
-  name = "non-default-filesystems";
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
 
-  nodes.machine =
-    { config, pkgs, lib, ... }:
-    let
-      disk = config.virtualisation.bootDevice;
-    in
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+{
+  btrfs = makeTest
     {
-      virtualisation.useDefaultFilesystems = false;
+      name = "non-default-filesystems-btrfs";
 
-      boot.initrd.availableKernelModules = [ "btrfs" ];
-      boot.supportedFilesystems = [ "btrfs" ];
+      nodes.machine =
+        { config, pkgs, lib, ... }:
+        let
+          disk = config.virtualisation.rootDevice;
+        in
+        {
+          virtualisation.rootDevice = "/dev/vda";
+          virtualisation.useDefaultFilesystems = false;
 
-      boot.initrd.postDeviceCommands = ''
-        FSTYPE=$(blkid -o value -s TYPE ${disk} || true)
-        if test -z "$FSTYPE"; then
-          modprobe btrfs
-          ${pkgs.btrfs-progs}/bin/mkfs.btrfs ${disk}
+          boot.initrd.availableKernelModules = [ "btrfs" ];
+          boot.supportedFilesystems = [ "btrfs" ];
 
-          mkdir /nixos
-          mount -t btrfs ${disk} /nixos
+          boot.initrd.postDeviceCommands = ''
+            FSTYPE=$(blkid -o value -s TYPE ${disk} || true)
+            if test -z "$FSTYPE"; then
+              modprobe btrfs
+              ${pkgs.btrfs-progs}/bin/mkfs.btrfs ${disk}
 
-          ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/root
-          ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/home
+              mkdir /nixos
+              mount -t btrfs ${disk} /nixos
 
-          umount /nixos
-        fi
-      '';
+              ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/root
+              ${pkgs.btrfs-progs}/bin/btrfs subvolume create /nixos/home
+
+              umount /nixos
+            fi
+          '';
 
-      virtualisation.fileSystems = {
-        "/" = {
-          device = disk;
-          fsType = "btrfs";
-          options = [ "subvol=/root" ];
+          virtualisation.fileSystems = {
+            "/" = {
+              device = disk;
+              fsType = "btrfs";
+              options = [ "subvol=/root" ];
+            };
+
+            "/home" = {
+              device = disk;
+              fsType = "btrfs";
+              options = [ "subvol=/home" ];
+            };
+          };
         };
 
-        "/home" = {
-          device = disk;
-          fsType = "btrfs";
-          options = [ "subvol=/home" ];
+      testScript = ''
+        machine.wait_for_unit("multi-user.target")
+
+        with subtest("BTRFS filesystems are mounted correctly"):
+          machine.succeed("grep -E '/dev/vda / btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts")
+          machine.succeed("grep -E '/dev/vda /home btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts")
+      '';
+    };
+
+  erofs =
+    let
+      fsImage = "/tmp/non-default-filesystem.img";
+    in
+    makeTest {
+      name = "non-default-filesystems-erofs";
+
+      nodes.machine = _: {
+        virtualisation.qemu.drives = [{
+          name = "non-default-filesystem";
+          file = fsImage;
+        }];
+
+        virtualisation.fileSystems."/non-default" = {
+          device = "/dev/vdb";
+          fsType = "erofs";
+          neededForBoot = true;
         };
       };
-    };
 
-  testScript = ''
-    machine.wait_for_unit("multi-user.target")
+      testScript = ''
+        import subprocess
+        import tempfile
 
-    with subtest("BTRFS filesystems are mounted correctly"):
-      machine.succeed("grep -E '/dev/vda / btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/root 0 0' /proc/mounts")
-      machine.succeed("grep -E '/dev/vda /home btrfs rw,relatime,space_cache=v2,subvolid=[0-9]+,subvol=/home 0 0' /proc/mounts")
-  '';
-})
+        with tempfile.TemporaryDirectory() as tmp_dir:
+          with open(f"{tmp_dir}/filesystem", "w") as f:
+              f.write("erofs")
+
+          subprocess.run([
+            "${pkgs.erofs-utils}/bin/mkfs.erofs",
+            "${fsImage}",
+            tmp_dir,
+          ])
+
+        machine.start()
+        machine.wait_for_unit("default.target")
+
+        file_contents = machine.succeed("cat /non-default/filesystem")
+        assert "erofs" in file_contents
+      '';
+    };
+}
diff --git a/nixos/tests/nzbget.nix b/nixos/tests/nzbget.nix
index fe5a4bc3df91f..e45031d5629c4 100644
--- a/nixos/tests/nzbget.nix
+++ b/nixos/tests/nzbget.nix
@@ -35,7 +35,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
         "${pkgs.nzbget}/bin/nzbget -n -o Control_iP=127.0.0.1 -o Control_port=6789 -o Control_password=tegbzn6789 -V"
     )
 
-    config = server.succeed("${nodes.server.config.systemd.services.nzbget.serviceConfig.ExecStart} --printconfig")
+    config = server.succeed("${nodes.server.systemd.services.nzbget.serviceConfig.ExecStart} --printconfig")
 
     # confirm the test settings are applied
     assert 'MainDir = "/var/lib/nzbget"' in config
diff --git a/nixos/tests/pam/zfs-key.nix b/nixos/tests/pam/zfs-key.nix
new file mode 100644
index 0000000000000..4f54c287e91a1
--- /dev/null
+++ b/nixos/tests/pam/zfs-key.nix
@@ -0,0 +1,83 @@
+import ../make-test-python.nix ({ ... }:
+
+  let
+    userPassword = "password";
+    mismatchPass = "mismatch";
+  in
+  {
+    name = "pam-zfs-key";
+
+    nodes.machine =
+      { ... }: {
+        boot.supportedFilesystems = [ "zfs" ];
+
+        networking.hostId = "12345678";
+
+        security.pam.zfs.enable = true;
+
+        users.users = {
+          alice = {
+            isNormalUser = true;
+            password = userPassword;
+          };
+          bob = {
+            isNormalUser = true;
+            password = userPassword;
+          };
+        };
+      };
+
+    testScript = { nodes, ... }:
+      let
+        homes = nodes.machine.security.pam.zfs.homes;
+        pool = builtins.head (builtins.split "/" homes);
+      in
+      ''
+        machine.wait_for_unit("multi-user.target")
+        machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+
+        with subtest("Create encrypted ZFS datasets"):
+          machine.succeed("truncate -s 64M /testpool.img")
+          machine.succeed("zpool create -O canmount=off '${pool}' /testpool.img")
+          machine.succeed("zfs create -o canmount=off -p '${homes}'")
+          machine.succeed("echo ${userPassword} | zfs create -o canmount=noauto -o encryption=on -o keyformat=passphrase '${homes}/alice'")
+          machine.succeed("zfs unload-key '${homes}/alice'")
+          machine.succeed("echo ${mismatchPass} | zfs create -o canmount=noauto -o encryption=on -o keyformat=passphrase '${homes}/bob'")
+          machine.succeed("zfs unload-key '${homes}/bob'")
+
+        with subtest("Switch to tty2"):
+          machine.fail("pgrep -f 'agetty.*tty2'")
+          machine.send_key("alt-f2")
+          machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+          machine.wait_for_unit("getty@tty2.service")
+          machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+
+        with subtest("Log in as user with home locked by login password"):
+          machine.wait_until_tty_matches("2", "login: ")
+          machine.send_chars("alice\n")
+          machine.wait_until_tty_matches("2", "login: alice")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("2", "Password: ")
+          machine.send_chars("${userPassword}\n")
+          machine.wait_until_succeeds("pgrep -u alice bash")
+          machine.succeed("mount | grep ${homes}/alice")
+
+        with subtest("Switch to tty3"):
+          machine.fail("pgrep -f 'agetty.*tty3'")
+          machine.send_key("alt-f3")
+          machine.wait_until_succeeds("[ $(fgconsole) = 3 ]")
+          machine.wait_for_unit("getty@tty3.service")
+          machine.wait_until_succeeds("pgrep -f 'agetty.*tty3'")
+
+        with subtest("Log in as user with home locked by password different from login"):
+          machine.wait_until_tty_matches("3", "login: ")
+          machine.send_chars("bob\n")
+          machine.wait_until_tty_matches("3", "login: bob")
+          machine.wait_until_succeeds("pgrep login")
+          machine.wait_until_tty_matches("3", "Password: ")
+          machine.send_chars("${userPassword}\n")
+          machine.wait_until_succeeds("pgrep -u bob bash")
+          machine.fail("mount | grep ${homes}/bob")
+      '';
+  }
+)
diff --git a/nixos/tests/pgadmin4.nix b/nixos/tests/pgadmin4.nix
index 6a9ce6ceae298..cb8de87c9ee31 100644
--- a/nixos/tests/pgadmin4.nix
+++ b/nixos/tests/pgadmin4.nix
@@ -9,6 +9,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
     imports = [ ./common/user-account.nix ];
 
     environment.systemPackages = with pkgs; [
+      wget
       curl
       pgadmin4-desktopmode
     ];
@@ -40,8 +41,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
     with subtest("Check pgadmin module"):
       machine.wait_for_unit("postgresql")
       machine.wait_for_unit("pgadmin")
-      machine.wait_until_succeeds("curl -s localhost:5051")
-      machine.wait_until_succeeds("curl -s localhost:5051/login | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+      machine.wait_until_succeeds("curl -sS localhost:5051")
+      machine.wait_until_succeeds("curl -sS localhost:5051/login | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+      # check for missing support files (css, js etc). Should catch not-generated files during build. See e.g. https://github.com/NixOS/nixpkgs/pull/229184
+      machine.succeed("wget -nv --level=1 --spider --recursive localhost:5051/login")
 
     # pgadmin4 module saves the configuration to /etc/pgadmin/config_system.py
     # pgadmin4-desktopmode tries to read that as well. This normally fails with a PermissionError, as the config file
@@ -51,7 +54,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
     # because of the wrong config for desktopmode.
     with subtest("Check pgadmin standalone desktop mode"):
       machine.execute("sudo -u alice pgadmin4 >&2 &", timeout=60)
-      machine.wait_until_succeeds("curl -s localhost:5050")
-      machine.wait_until_succeeds("curl -s localhost:5050/browser/ | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+      machine.wait_until_succeeds("curl -sS localhost:5050")
+      machine.wait_until_succeeds("curl -sS localhost:5050/browser/ | grep \"<title>pgAdmin 4</title>\" > /dev/null")
+      machine.succeed("wget -nv --level=1 --spider --recursive localhost:5050/browser")
   '';
 })
diff --git a/nixos/tests/power-profiles-daemon.nix b/nixos/tests/power-profiles-daemon.nix
index 278e94711830a..c887cde4b829e 100644
--- a/nixos/tests/power-profiles-daemon.nix
+++ b/nixos/tests/power-profiles-daemon.nix
@@ -6,6 +6,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
     maintainers = [ mvnetbiz ];
   };
   nodes.machine = { pkgs, ... }: {
+    security.polkit.enable = true;
     services.power-profiles-daemon.enable = true;
     environment.systemPackages = [ pkgs.glib ];
   };
diff --git a/nixos/tests/pppd.nix b/nixos/tests/pppd.nix
index e714a6c21a6c8..d599f918036f7 100644
--- a/nixos/tests/pppd.nix
+++ b/nixos/tests/pppd.nix
@@ -28,7 +28,7 @@ import ./make-test-python.nix (
             "ppp/pppoe-server-options".text = ''
               lcp-echo-interval 10
               lcp-echo-failure 2
-              plugin rp-pppoe.so
+              plugin pppoe.so
               require-chap
               nobsdcomp
               noccp
@@ -43,7 +43,7 @@ import ./make-test-python.nix (
           enable = true;
           peers.test = {
             config = ''
-              plugin rp-pppoe.so eth1
+              plugin pppoe.so eth1
               name "flynn"
               noipdefault
               persist
diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix
index 684df9c39246c..42183625c7c93 100644
--- a/nixos/tests/predictable-interface-names.nix
+++ b/nixos/tests/predictable-interface-names.nix
@@ -8,25 +8,48 @@ let
   testCombinations = pkgs.lib.cartesianProductOfSets {
     predictable = [true false];
     withNetworkd = [true false];
+    systemdStage1 = [true false];
   };
-in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd }: {
+in pkgs.lib.listToAttrs (builtins.map ({ predictable, withNetworkd, systemdStage1 }: {
   name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
-       + pkgs.lib.optionalString withNetworkd "Networkd";
+       + pkgs.lib.optionalString withNetworkd "Networkd"
+       + pkgs.lib.optionalString systemdStage1 "SystemdStage1";
   value = makeTest {
-    name = "${pkgs.lib.optionalString (!predictable) "un"}predictableInterfaceNames${pkgs.lib.optionalString withNetworkd "-with-networkd"}";
+    name = pkgs.lib.optionalString (!predictable) "un" + "predictableInterfaceNames"
+         + pkgs.lib.optionalString withNetworkd "-with-networkd"
+         + pkgs.lib.optionalString systemdStage1 "-systemd-stage-1";
     meta = {};
 
-    nodes.machine = { lib, ... }: {
+    nodes.machine = { lib, ... }: let
+      script = ''
+        ip link
+        if ${lib.optionalString predictable "!"} ip link show eth0; then
+          echo Success
+        else
+          exit 1
+        fi
+      '';
+    in {
       networking.usePredictableInterfaceNames = lib.mkForce predictable;
       networking.useNetworkd = withNetworkd;
       networking.dhcpcd.enable = !withNetworkd;
       networking.useDHCP = !withNetworkd;
 
       # Check if predictable interface names are working in stage-1
-      boot.initrd.postDeviceCommands = ''
-        ip link
-        ip link show eth0 ${if predictable then "&&" else "||"} exit 1
-      '';
+      boot.initrd.postDeviceCommands = script;
+
+      boot.initrd.systemd = lib.mkIf systemdStage1 {
+        enable = true;
+        initrdBin = [ pkgs.iproute2 ];
+        services.systemd-udev-settle.wantedBy = ["initrd.target"];
+        services.check-interfaces = {
+          requiredBy = ["initrd.target"];
+          after = ["systemd-udev-settle.service"];
+          serviceConfig.Type = "oneshot";
+          path = [ pkgs.iproute2 ];
+          inherit script;
+        };
+      };
     };
 
     testScript = ''
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 82d50da638183..adc2b467be514 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -1396,7 +1396,10 @@ let
       '';
     };
 
-    wireguard = let snakeoil = import ./wireguard/snakeoil-keys.nix; in
+    wireguard = let
+      snakeoil = import ./wireguard/snakeoil-keys.nix;
+      publicKeyWithoutNewlines = replaceStrings [ "\n" ] [ "" ] snakeoil.peer1.publicKey;
+    in
       {
         exporterConfig.enable = true;
         metricProvider = {
@@ -1418,7 +1421,7 @@ let
           wait_for_unit("prometheus-wireguard-exporter.service")
           wait_for_open_port(9586)
           wait_until_succeeds(
-              "curl -sSf http://localhost:9586/metrics | grep '${snakeoil.peer1.publicKey}'"
+              "curl -sSf http://localhost:9586/metrics | grep '${publicKeyWithoutNewlines}'"
           )
         '';
       };
diff --git a/nixos/tests/promscale.nix b/nixos/tests/promscale.nix
new file mode 100644
index 0000000000000..d4825b6d7f551
--- /dev/null
+++ b/nixos/tests/promscale.nix
@@ -0,0 +1,60 @@
+# mostly copied from ./timescaledb.nix which was copied from ./postgresql.nix
+# as it seemed unapproriate to test additional extensions for postgresql there.
+
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
+  test-sql = pkgs.writeText "postgresql-test" ''
+    CREATE USER promscale SUPERUSER PASSWORD 'promscale';
+    CREATE DATABASE promscale OWNER promscale;
+  '';
+
+  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+    name = postgresql-name;
+    meta = with pkgs.lib.maintainers; {
+      maintainers = [ anpin ];
+    };
+
+    nodes.machine = { config, pkgs, ... }:
+      {
+        services.postgresql = {
+          enable = true;
+          package = postgresql-package;
+          extraPlugins = with postgresql-package.pkgs; [
+            timescaledb
+            promscale_extension
+          ];
+          settings = { shared_preload_libraries = "timescaledb, promscale"; };
+        };
+        environment.systemPackages = with pkgs; [ promscale ];
+      };
+
+    testScript = ''
+      machine.start()
+      machine.wait_for_unit("postgresql")
+      with subtest("Postgresql with extensions timescaledb and promscale is available just after unit start"):
+          print(machine.succeed("sudo -u postgres psql -f ${test-sql}"))
+          machine.succeed("sudo -u postgres psql promscale -c 'SHOW shared_preload_libraries;' | grep promscale")
+          machine.succeed(
+            "promscale --db.name promscale --db.password promscale --db.user promscale --db.ssl-mode allow --startup.install-extensions --startup.only"
+          )
+      machine.succeed("sudo -u postgres psql promscale -c 'SELECT ps_trace.get_trace_retention_period();' | grep '(1 row)'")
+      machine.shutdown()
+    '';
+  };
+  #version 15 is not supported yet
+  applicablePostgresqlVersions = filterAttrs (_: value: versionAtLeast value.version "12" && !(versionAtLeast value.version "15")) postgresql-versions;
+in
+mapAttrs'
+  (name: package: {
+    inherit name;
+    value = make-postgresql-test name package;
+  })
+  applicablePostgresqlVersions
diff --git a/nixos/tests/pufferpanel.nix b/nixos/tests/pufferpanel.nix
new file mode 100644
index 0000000000000..e7b09c13f90bd
--- /dev/null
+++ b/nixos/tests/pufferpanel.nix
@@ -0,0 +1,74 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "pufferpanel";
+  meta.maintainers = [ lib.maintainers.tie ];
+
+  nodes.machine = { pkgs, ... }: {
+    environment.systemPackages = [ pkgs.pufferpanel ];
+    services.pufferpanel = {
+      enable = true;
+      extraPackages = [ pkgs.netcat ];
+      environment = {
+        PUFFER_PANEL_REGISTRATIONENABLED = "false";
+        PUFFER_PANEL_SETTINGS_COMPANYNAME = "NixOS";
+      };
+    };
+  };
+
+  testScript = ''
+    import shlex
+    import json
+
+    curl = "curl --fail-with-body --silent"
+    baseURL = "http://localhost:8080"
+    adminName = "admin"
+    adminEmail = "admin@nixos.org"
+    adminPass = "admin"
+    adminCreds = json.dumps({
+      "email": adminEmail,
+      "password": adminPass,
+    })
+    stopCode = 9 # SIGKILL
+    serverPort = 1337
+    serverDefinition = json.dumps({
+      "name": "netcat",
+      "node": 0,
+      "users": [
+        adminName,
+      ],
+      "type": "netcat",
+      "run": {
+        "stopCode": stopCode,
+        "command": f"nc -l {serverPort}",
+      },
+      "environment": {
+        "type": "standard",
+      },
+    })
+
+    start_all()
+
+    machine.wait_for_unit("pufferpanel.service")
+    machine.wait_for_open_port(5657) # SFTP
+    machine.wait_for_open_port(8080) # HTTP
+
+    # Note that PufferPanel does not initialize database unless necessary.
+    # /api/config endpoint creates database file and triggers migrations.
+    # On success, we run a command to create administrator user that we use to
+    # interact with HTTP API.
+    resp = json.loads(machine.succeed(f"{curl} {baseURL}/api/config"))
+    assert resp["branding"]["name"] == "NixOS", "Invalid company name in configuration"
+    assert resp["registrationEnabled"] == False, "Expected registration to be disabled"
+
+    machine.succeed(f"pufferpanel --workDir /var/lib/pufferpanel user add --admin --name {adminName} --email {adminEmail} --password {adminPass}")
+
+    resp = json.loads(machine.succeed(f"{curl} -d '{adminCreds}' {baseURL}/auth/login"))
+    assert "servers.admin" in resp["scopes"], "User is not administrator"
+    token = resp["session"]
+    authHeader = shlex.quote(f"Authorization: Bearer {token}")
+
+    resp = json.loads(machine.succeed(f"{curl} -H {authHeader} -H 'Content-Type: application/json' -d '{serverDefinition}' {baseURL}/api/servers"))
+    serverID = resp["id"]
+    machine.succeed(f"{curl} -X POST -H {authHeader} {baseURL}/proxy/daemon/server/{serverID}/start")
+    machine.wait_for_open_port(serverPort)
+  '';
+})
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 1071fbada74fe..626049e73341f 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -2,18 +2,18 @@ import ./make-test-python.nix (
   { pkgs, ... }:
 
   let
-    remoteRepository = "/tmp/restic-backup";
-    remoteFromFileRepository = "/tmp/restic-backup-from-file";
-    rcloneRepository = "rclone:local:/tmp/restic-rclone-backup";
+    remoteRepository = "/root/restic-backup";
+    remoteFromFileRepository = "/root/restic-backup-from-file";
+    rcloneRepository = "rclone:local:/root/restic-rclone-backup";
 
     backupPrepareCommand = ''
-      touch /tmp/backupPrepareCommand
-      test ! -e /tmp/backupCleanupCommand
+      touch /root/backupPrepareCommand
+      test ! -e /root/backupCleanupCommand
     '';
 
     backupCleanupCommand = ''
-      rm /tmp/backupPrepareCommand
-      touch /tmp/backupCleanupCommand
+      rm /root/backupPrepareCommand
+      touch /root/backupCleanupCommand
     '';
 
     testDir = pkgs.stdenvNoCC.mkDerivation {
@@ -81,7 +81,7 @@ import ./make-test-python.nix (
               inherit passwordFile paths;
               repository = "some-fake-repository";
               package = pkgs.writeShellScriptBin "restic" ''
-                echo "$@" >> /tmp/fake-restic.log;
+                echo "$@" >> /root/fake-restic.log;
               '';
 
               pruneOpts = [ "--keep-last 1" ];
@@ -100,18 +100,18 @@ import ./make-test-python.nix (
           "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots",
           '${pkgs.restic}/bin/restic -r ${remoteFromFileRepository} -p ${passwordFile} snapshots"',
           "${pkgs.restic}/bin/restic -r ${rcloneRepository} -p ${passwordFile} snapshots",
-          "grep 'backup.* /opt' /tmp/fake-restic.log",
+          "grep 'backup.* /opt' /root/fake-restic.log",
       )
       server.succeed(
           # set up
           "cp -rT ${testDir} /opt",
           "touch /opt/excluded_file_1 /opt/excluded_file_2",
-          "mkdir -p /tmp/restic-rclone-backup",
+          "mkdir -p /root/restic-rclone-backup",
 
           # test that remotebackup runs custom commands and produces a snapshot
           "timedatectl set-time '2016-12-13 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /tmp/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           '${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
 
           # test that restoring that snapshot produces the same directory
@@ -129,33 +129,33 @@ import ./make-test-python.nix (
 
           # test that custompackage runs both `restic backup` and `restic check` with reasonable commandlines
           "systemctl start restic-backups-custompackage.service",
-          "grep 'backup.* /opt' /tmp/fake-restic.log",
-          "grep 'check.* --some-check-option' /tmp/fake-restic.log",
+          "grep 'backup.* /opt' /root/fake-restic.log",
+          "grep 'check.* --some-check-option' /root/fake-restic.log",
 
           # test that we can create four snapshots in remotebackup and rclonebackup
           "timedatectl set-time '2017-12-13 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /tmp/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
 
           "timedatectl set-time '2018-12-13 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /tmp/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
 
           "timedatectl set-time '2018-12-14 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /tmp/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
 
           "timedatectl set-time '2018-12-15 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /tmp/backupCleanupCommand",
+          "rm /root/backupCleanupCommand",
           "systemctl start restic-backups-rclonebackup.service",
 
           "timedatectl set-time '2018-12-16 13:45'",
           "systemctl start restic-backups-remotebackup.service",
-          "rm /tmp/backupCleanupCommand",
+          "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"',
diff --git a/nixos/tests/rshim.nix b/nixos/tests/rshim.nix
new file mode 100644
index 0000000000000..bb5cce028ae79
--- /dev/null
+++ b/nixos/tests/rshim.nix
@@ -0,0 +1,25 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  basic = makeTest {
+    name = "rshim";
+    meta.maintainers = with maintainers; [ nikstur ];
+
+    nodes.machine = { config, pkgs, ... }: {
+      services.rshim.enable = true;
+    };
+
+    testScript = { nodes, ... }: ''
+      machine.start()
+      machine.wait_for_unit("multi-user.target")
+
+      print(machine.succeed("systemctl status rshim.service"))
+    '';
+  };
+}
diff --git a/nixos/tests/sssd-ldap.nix b/nixos/tests/sssd-ldap.nix
index ff83e96068a96..60f3b1a415daf 100644
--- a/nixos/tests/sssd-ldap.nix
+++ b/nixos/tests/sssd-ldap.nix
@@ -6,17 +6,33 @@ let
   ldapRootPassword = "foobar";
 
   testUser = "alice";
-in import ./make-test-python.nix ({pkgs, ...}: {
+  testPassword = "foobar";
+  testNewPassword = "barfoo";
+in
+import ./make-test-python.nix ({ pkgs, ... }: {
   name = "sssd-ldap";
 
   meta = with pkgs.lib.maintainers; {
-    maintainers = [ bbigras ];
+    maintainers = [ bbigras s1341 ];
   };
 
   nodes.machine = { pkgs, ... }: {
+    security.pam.services.systemd-user.makeHomeDir = true;
+    environment.etc."cert.pem".text = builtins.readFile ./common/acme/server/acme.test.cert.pem;
+    environment.etc."key.pem".text = builtins.readFile ./common/acme/server/acme.test.key.pem;
     services.openldap = {
       enable = true;
+      urlList = [ "ldap:///" "ldaps:///" ];
       settings = {
+        attrs = {
+          olcTLSCACertificateFile = "/etc/cert.pem";
+          olcTLSCertificateFile = "/etc/cert.pem";
+          olcTLSCertificateKeyFile = "/etc/key.pem";
+          olcTLSCipherSuite = "HIGH:MEDIUM:+3DES:+RC4:+aNULL";
+          olcTLSCRLCheck = "none";
+          olcTLSVerifyClient = "never";
+          olcTLSProtocolMin = "3.1";
+        };
         children = {
           "cn=schema".includes = [
             "${pkgs.openldap}/etc/schema/core.ldif"
@@ -32,6 +48,23 @@ in import ./make-test-python.nix ({pkgs, ...}: {
               olcSuffix = dbSuffix;
               olcRootDN = "cn=${ldapRootUser},${dbSuffix}";
               olcRootPW = ldapRootPassword;
+              olcAccess = [
+                /*
+                  custom access rules for userPassword attributes
+                  */
+                ''
+                  {0}to attrs=userPassword
+                                    by self write
+                                    by anonymous auth
+                                    by * none''
+
+                /*
+                  allow read on anything else
+                  */
+                ''
+                  {1}to *
+                                    by * read''
+              ];
             };
           };
         };
@@ -55,7 +88,7 @@ in import ./make-test-python.nix ({pkgs, ...}: {
           dn: uid=${testUser},ou=accounts,ou=posix,${dbSuffix}
           objectClass: person
           objectClass: posixAccount
-          # userPassword: somePasswordHash
+          userPassword: ${testPassword}
           homeDirectory: /home/${testUser}
           uidNumber: 1234
           gidNumber: 1234
@@ -78,7 +111,9 @@ in import ./make-test-python.nix ({pkgs, ...}: {
         [domain/${dbDomain}]
         auth_provider = ldap
         id_provider = ldap
-        ldap_uri = ldap://127.0.0.1:389
+        ldap_uri = ldaps://127.0.0.1:636
+        ldap_tls_reqcert = allow
+        ldap_tls_cacert = /etc/cert.pem
         ldap_search_base = ${dbSuffix}
         ldap_default_bind_dn = cn=${ldapRootUser},${dbSuffix}
         ldap_default_authtok_type = password
@@ -97,5 +132,42 @@ in import ./make-test-python.nix ({pkgs, ...}: {
     else:
       machine.wait_for_console_text("Backend is online")
       machine.succeed("getent passwd ${testUser}")
+
+    with subtest("Log in as ${testUser}"):
+        machine.wait_until_tty_matches("1", "login: ")
+        machine.send_chars("${testUser}\n")
+        machine.wait_until_tty_matches("1", "login: ${testUser}")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches("1", "Password: ")
+        machine.send_chars("${testPassword}\n")
+        machine.wait_until_succeeds("pgrep -u ${testUser} bash")
+        machine.send_chars("touch done\n")
+        machine.wait_for_file("/home/${testUser}/done")
+
+    with subtest("Change ${testUser}'s password"):
+        machine.send_chars("passwd\n")
+        machine.wait_until_tty_matches("1", "Current Password: ")
+        machine.send_chars("${testPassword}\n")
+        machine.wait_until_tty_matches("1", "New Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_tty_matches("1", "Reenter new Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_tty_matches("1", "passwd: password updated successfully")
+
+    with subtest("Log in as ${testUser} with new password in virtual console 2"):
+        machine.send_key("alt-f2")
+        machine.wait_until_succeeds("[ $(fgconsole) = 2 ]")
+        machine.wait_for_unit("getty@tty2.service")
+        machine.wait_until_succeeds("pgrep -f 'agetty.*tty2'")
+
+        machine.wait_until_tty_matches("2", "login: ")
+        machine.send_chars("${testUser}\n")
+        machine.wait_until_tty_matches("2", "login: ${testUser}")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches("2", "Password: ")
+        machine.send_chars("${testNewPassword}\n")
+        machine.wait_until_succeeds("pgrep -u ${testUser} bash")
+        machine.send_chars("touch done2\n")
+        machine.wait_for_file("/home/${testUser}/done2")
   '';
 })
diff --git a/nixos/tests/swap-file-btrfs.nix b/nixos/tests/swap-file-btrfs.nix
index 4f73942b5f32b..d9fcd2be1160a 100644
--- a/nixos/tests/swap-file-btrfs.nix
+++ b/nixos/tests/swap-file-btrfs.nix
@@ -9,7 +9,7 @@ import ./make-test-python.nix ({ lib, ... }:
     {
       virtualisation.useDefaultFilesystems = false;
 
-      virtualisation.bootDevice = "/dev/vda";
+      virtualisation.rootDevice = "/dev/vda";
 
       boot.initrd.postDeviceCommands = ''
         ${pkgs.btrfs-progs}/bin/mkfs.btrfs --label root /dev/vda
diff --git a/nixos/tests/swap-partition.nix b/nixos/tests/swap-partition.nix
index 2279630b57b8f..ddcaeb95453e1 100644
--- a/nixos/tests/swap-partition.nix
+++ b/nixos/tests/swap-partition.nix
@@ -7,7 +7,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }:
     {
       virtualisation.useDefaultFilesystems = false;
 
-      virtualisation.bootDevice = "/dev/vda1";
+      virtualisation.rootDevice = "/dev/vda1";
 
       boot.initrd.postDeviceCommands = ''
         if ! test -b /dev/vda1; then
diff --git a/nixos/tests/swap-random-encryption.nix b/nixos/tests/swap-random-encryption.nix
new file mode 100644
index 0000000000000..9e919db65dde8
--- /dev/null
+++ b/nixos/tests/swap-random-encryption.nix
@@ -0,0 +1,80 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }:
+{
+  name = "swap-random-encryption";
+
+  nodes.machine =
+    { config, pkgs, lib, ... }:
+    {
+      environment.systemPackages = [ pkgs.cryptsetup ];
+
+      virtualisation.useDefaultFilesystems = false;
+
+      virtualisation.rootDevice = "/dev/vda1";
+
+      boot.initrd.postDeviceCommands = ''
+        if ! test -b /dev/vda1; then
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mklabel msdos
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary 1MiB -250MiB
+          ${pkgs.parted}/bin/parted --script /dev/vda -- mkpart primary -250MiB 100%
+          sync
+        fi
+
+        FSTYPE=$(blkid -o value -s TYPE /dev/vda1 || true)
+        if test -z "$FSTYPE"; then
+          ${pkgs.e2fsprogs}/bin/mke2fs -t ext4 -L root /dev/vda1
+        fi
+      '';
+
+      virtualisation.fileSystems = {
+        "/" = {
+          device = "/dev/disk/by-label/root";
+          fsType = "ext4";
+        };
+      };
+
+      swapDevices = [
+        {
+          device = "/dev/vda2";
+
+          randomEncryption = {
+            enable = true;
+            cipher = "aes-xts-plain64";
+            keySize = 512;
+            sectorSize = 4096;
+          };
+        }
+      ];
+    };
+
+  testScript = ''
+    machine.wait_for_unit("multi-user.target")
+
+    with subtest("Swap is active"):
+      # Doesn't matter if the numbers reported by `free` are slightly off due to unit conversions.
+      machine.succeed("free -h | grep -E 'Swap:\s+2[45][0-9]Mi'")
+
+    with subtest("Swap device has 4k sector size"):
+      import json
+      result = json.loads(machine.succeed("lsblk -Jo PHY-SEC,LOG-SEC /dev/mapper/dev-vda2"))
+      block_devices = result["blockdevices"]
+      if len(block_devices) != 1:
+        raise Exception ("lsblk output did not report exactly one block device")
+
+      swapDevice = block_devices[0];
+      if not (swapDevice["phy-sec"] == 4096 and swapDevice["log-sec"] == 4096):
+        raise Exception ("swap device does not have the sector size specified in the configuration")
+
+    with subtest("Swap encrypt has assigned cipher and keysize"):
+      import re
+
+      results = machine.succeed("cryptsetup status dev-vda2").splitlines()
+
+      cipher_pattern = re.compile(r"\s*cipher:\s+aes-xts-plain64\s*")
+      if not any(cipher_pattern.fullmatch(line) for line in results):
+        raise Exception ("swap device encryption does not use the cipher specified in the configuration")
+
+      key_size_pattern = re.compile(r"\s*keysize:\s+512\s+bits\s*")
+      if not any(key_size_pattern.fullmatch(line) for line in results):
+        raise Exception ("swap device encryption does not use the key size specified in the configuration")
+  '';
+})
diff --git a/nixos/tests/syncthing-init.nix b/nixos/tests/syncthing-init.nix
index fcd90739e6a55..435813e58066f 100644
--- a/nixos/tests/syncthing-init.nix
+++ b/nixos/tests/syncthing-init.nix
@@ -9,14 +9,16 @@ in {
   nodes.machine = {
     services.syncthing = {
       enable = true;
-      devices.testDevice = {
-        id = testId;
+      settings = {
+        devices.testDevice = {
+          id = testId;
+        };
+        folders.testFolder = {
+          path = "/tmp/test";
+          devices = [ "testDevice" ];
+        };
+        gui.user = "guiUser";
       };
-      folders.testFolder = {
-        path = "/tmp/test";
-        devices = [ "testDevice" ];
-      };
-      extraOptions.gui.user = "guiUser";
     };
   };
 
diff --git a/nixos/tests/systemd-initrd-btrfs-raid.nix b/nixos/tests/systemd-initrd-btrfs-raid.nix
index 40fd2d4dc611c..c9cdf0060b1bf 100644
--- a/nixos/tests/systemd-initrd-btrfs-raid.nix
+++ b/nixos/tests/systemd-initrd-btrfs-raid.nix
@@ -21,14 +21,14 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       fileSystems = lib.mkVMOverride {
         "/".fsType = lib.mkForce "btrfs";
       };
-      virtualisation.bootDevice = "/dev/vdc";
+      virtualisation.rootDevice = "/dev/vdb";
     };
   };
 
   testScript = ''
     # Create RAID
-    machine.succeed("mkfs.btrfs -d raid0 /dev/vdc /dev/vdd")
-    machine.succeed("mkdir -p /mnt && mount /dev/vdc /mnt && echo hello > /mnt/test && umount /mnt")
+    machine.succeed("mkfs.btrfs -d raid0 /dev/vdb /dev/vdc")
+    machine.succeed("mkdir -p /mnt && mount /dev/vdb /mnt && echo hello > /mnt/test && umount /mnt")
 
     # Boot from the RAID
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-btrfs-raid.conf")
@@ -38,7 +38,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
 
     # Ensure we have successfully booted from the RAID
     assert "(initrd)" in machine.succeed("systemd-analyze")  # booted with systemd in stage 1
-    assert "/dev/vdc on / type btrfs" in machine.succeed("mount")
+    assert "/dev/vdb on / type btrfs" in machine.succeed("mount")
     assert "hello" in machine.succeed("cat /test")
     assert "Total devices 2" in machine.succeed("btrfs filesystem show")
   '';
diff --git a/nixos/tests/systemd-initrd-luks-fido2.nix b/nixos/tests/systemd-initrd-luks-fido2.nix
index 133e552a3dc99..e80d95f79c7ea 100644
--- a/nixos/tests/systemd-initrd-luks-fido2.nix
+++ b/nixos/tests/systemd-initrd-luks-fido2.nix
@@ -19,19 +19,19 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     specialisation.boot-luks.configuration = {
       boot.initrd.luks.devices = lib.mkVMOverride {
         cryptroot = {
-          device = "/dev/vdc";
+          device = "/dev/vdb";
           crypttabExtraOpts = [ "fido2-device=auto" ];
         };
       };
-      virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
     };
   };
 
   testScript = ''
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
-    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
-    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --fido2-device=auto /dev/vdc |& systemd-cat")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --fido2-device=auto /dev/vdb |& systemd-cat")
 
     # Boot from the encrypted disk
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
diff --git a/nixos/tests/systemd-initrd-luks-keyfile.nix b/nixos/tests/systemd-initrd-luks-keyfile.nix
index 25c0c5bd866d3..257243d92a1d6 100644
--- a/nixos/tests/systemd-initrd-luks-keyfile.nix
+++ b/nixos/tests/systemd-initrd-luks-keyfile.nix
@@ -27,11 +27,11 @@ in {
     specialisation.boot-luks.configuration = {
       boot.initrd.luks.devices = lib.mkVMOverride {
         cryptroot = {
-          device = "/dev/vdc";
+          device = "/dev/vdb";
           keyFile = "/etc/cryptroot.key";
         };
       };
-      virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
       boot.initrd.secrets."/etc/cryptroot.key" = keyfile;
     };
   };
@@ -39,7 +39,7 @@ in {
   testScript = ''
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
-    machine.succeed("cryptsetup luksFormat -q --iter-time=1 -d ${keyfile} /dev/vdc")
+    machine.succeed("cryptsetup luksFormat -q --iter-time=1 -d ${keyfile} /dev/vdb")
 
     # Boot from the encrypted disk
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
diff --git a/nixos/tests/systemd-initrd-luks-password.nix b/nixos/tests/systemd-initrd-luks-password.nix
index 55d0b4324b400..2dd3f304e82a0 100644
--- a/nixos/tests/systemd-initrd-luks-password.nix
+++ b/nixos/tests/systemd-initrd-luks-password.nix
@@ -19,10 +19,10 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     specialisation.boot-luks.configuration = {
       boot.initrd.luks.devices = lib.mkVMOverride {
         # We have two disks and only type one password - key reuse is in place
-        cryptroot.device = "/dev/vdc";
-        cryptroot2.device = "/dev/vdd";
+        cryptroot.device = "/dev/vdb";
+        cryptroot2.device = "/dev/vdc";
       };
-      virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
       # test mounting device unlocked in initrd after switching root
       virtualisation.fileSystems."/cryptroot2".device = "/dev/mapper/cryptroot2";
     };
@@ -31,9 +31,9 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   testScript = ''
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
     machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
-    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdd -")
-    machine.succeed("echo -n supersecret | cryptsetup luksOpen   -q               /dev/vdd cryptroot2")
+    machine.succeed("echo -n supersecret | cryptsetup luksOpen   -q               /dev/vdc cryptroot2")
     machine.succeed("mkfs.ext4 /dev/mapper/cryptroot2")
 
     # Boot from the encrypted disk
@@ -47,7 +47,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     machine.send_console("supersecret\n")
     machine.wait_for_unit("multi-user.target")
 
-    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount")
+    assert "/dev/mapper/cryptroot on / type ext4" in machine.succeed("mount"), "/dev/mapper/cryptroot do not appear in mountpoints list"
     assert "/dev/mapper/cryptroot2 on /cryptroot2 type ext4" in machine.succeed("mount")
   '';
 })
diff --git a/nixos/tests/systemd-initrd-luks-tpm2.nix b/nixos/tests/systemd-initrd-luks-tpm2.nix
index 085088d2ee25e..734ef38579f0d 100644
--- a/nixos/tests/systemd-initrd-luks-tpm2.nix
+++ b/nixos/tests/systemd-initrd-luks-tpm2.nix
@@ -21,11 +21,11 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
     specialisation.boot-luks.configuration = {
       boot.initrd.luks.devices = lib.mkVMOverride {
         cryptroot = {
-          device = "/dev/vdc";
+          device = "/dev/vdb";
           crypttabExtraOpts = [ "tpm2-device=auto" ];
         };
       };
-      virtualisation.bootDevice = "/dev/mapper/cryptroot";
+      virtualisation.rootDevice = "/dev/mapper/cryptroot";
     };
   };
 
@@ -55,8 +55,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
 
     # Create encrypted volume
     machine.wait_for_unit("multi-user.target")
-    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdc -")
-    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --tpm2-pcrs= --tpm2-device=auto /dev/vdc |& systemd-cat")
+    machine.succeed("echo -n supersecret | cryptsetup luksFormat -q --iter-time=1 /dev/vdb -")
+    machine.succeed("PASSWORD=supersecret SYSTEMD_LOG_LEVEL=debug systemd-cryptenroll --tpm2-pcrs= --tpm2-device=auto /dev/vdb |& systemd-cat")
 
     # Boot from the encrypted disk
     machine.succeed("bootctl set-default nixos-generation-1-specialisation-boot-luks.conf")
diff --git a/nixos/tests/systemd-initrd-networkd-ssh.nix b/nixos/tests/systemd-initrd-networkd-ssh.nix
new file mode 100644
index 0000000000000..943552613be99
--- /dev/null
+++ b/nixos/tests/systemd-initrd-networkd-ssh.nix
@@ -0,0 +1,82 @@
+import ./make-test-python.nix ({ lib, ... }: {
+  name = "systemd-initrd-network-ssh";
+  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+
+  nodes = with lib; {
+    server = { config, pkgs, ... }: {
+      environment.systemPackages = [pkgs.cryptsetup];
+      boot.loader.systemd-boot.enable = true;
+      boot.loader.timeout = 0;
+      virtualisation = {
+        emptyDiskImages = [ 4096 ];
+        useBootLoader = true;
+        useEFIBoot = true;
+      };
+
+      specialisation.encrypted-root.configuration = {
+        virtualisation.bootDevice = "/dev/mapper/root";
+        boot.initrd.luks.devices = lib.mkVMOverride {
+          root.device = "/dev/vdc";
+        };
+        boot.initrd.systemd.enable = true;
+        boot.initrd.network = {
+          enable = true;
+          ssh = {
+            enable = true;
+            authorizedKeys = [ (readFile ./initrd-network-ssh/id_ed25519.pub) ];
+            port = 22;
+            # Terrible hack so it works with useBootLoader
+            hostKeys = [ { outPath = "${./initrd-network-ssh/ssh_host_ed25519_key}"; } ];
+          };
+        };
+      };
+    };
+
+    client = { config, ... }: {
+      environment.etc = {
+        knownHosts = {
+          text = concatStrings [
+            "server,"
+            "${
+              toString (head (splitString " " (toString
+                (elemAt (splitString "\n" config.networking.extraHosts) 2))))
+            } "
+            "${readFile ./initrd-network-ssh/ssh_host_ed25519_key.pub}"
+          ];
+        };
+        sshKey = {
+          source = ./initrd-network-ssh/id_ed25519;
+          mode = "0600";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+
+    def ssh_is_up(_) -> bool:
+        status, _ = client.execute("nc -z server 22")
+        return status == 0
+
+    server.wait_for_unit("multi-user.target")
+    server.succeed(
+        "echo somepass | cryptsetup luksFormat --type=luks2 /dev/vdc",
+        "bootctl set-default nixos-generation-1-specialisation-encrypted-root.conf",
+        "sync",
+    )
+    server.shutdown()
+    server.start()
+
+    client.wait_for_unit("network.target")
+    with client.nested("waiting for SSH server to come up"):
+        retry(ssh_is_up)
+
+    client.succeed(
+        "echo somepass | ssh -i /etc/sshKey -o UserKnownHostsFile=/etc/knownHosts server 'systemd-tty-ask-password-agent' & exit"
+    )
+
+    server.wait_for_unit("multi-user.target")
+    server.succeed("mount | grep '/dev/mapper/root on /'")
+  '';
+})
diff --git a/nixos/tests/systemd-initrd-networkd.nix b/nixos/tests/systemd-initrd-networkd.nix
new file mode 100644
index 0000000000000..00ecbec5613c4
--- /dev/null
+++ b/nixos/tests/systemd-initrd-networkd.nix
@@ -0,0 +1,74 @@
+import ./make-test-python.nix ({ pkgs, lib, ... }: {
+  name = "systemd-initrd-network";
+  meta.maintainers = [ lib.maintainers.elvishjerricco ];
+
+  nodes = let
+    mkFlushTest = flush: script: { ... }: {
+      boot.initrd.systemd.enable = true;
+      boot.initrd.network = {
+        enable = true;
+        flushBeforeStage2 = flush;
+      };
+      systemd.services.check-flush = {
+        requiredBy = ["multi-user.target"];
+        before = ["network-pre.target" "multi-user.target"];
+        unitConfig.DefaultDependencies = false;
+        serviceConfig.Type = "oneshot";
+        path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+        inherit script;
+      };
+    };
+  in {
+    basic = { ... }: {
+      boot.initrd.network.enable = true;
+
+      boot.initrd.systemd = {
+        enable = true;
+        # Enable network-online to fail the test in case of timeout
+        network.wait-online.timeout = 10;
+        network.wait-online.anyInterface = true;
+        targets.network-online.requiredBy = [ "initrd.target" ];
+        services.systemd-networkd-wait-online.requiredBy =
+          [ "network-online.target" ];
+
+          initrdBin = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+          services.check = {
+            requiredBy = [ "initrd.target" ];
+            before = [ "initrd.target" ];
+            after = [ "network-online.target" ];
+            serviceConfig.Type = "oneshot";
+            path = [ pkgs.iproute2 pkgs.iputils pkgs.gnugrep ];
+            script = ''
+              ip addr | grep 10.0.2.15 || exit 1
+              ping -c1 10.0.2.2 || exit 1
+            '';
+          };
+      };
+    };
+
+    doFlush = mkFlushTest true ''
+      if ip addr | grep 10.0.2.15; then
+        echo "Network configuration survived switch-root; flushBeforeStage2 failed"
+        exit 1
+      fi
+    '';
+
+    dontFlush = mkFlushTest false ''
+      if ! (ip addr | grep 10.0.2.15); then
+        echo "Network configuration didn't survive switch-root"
+        exit 1
+      fi
+    '';
+  };
+
+  testScript = ''
+    start_all()
+    basic.wait_for_unit("multi-user.target")
+    doFlush.wait_for_unit("multi-user.target")
+    dontFlush.wait_for_unit("multi-user.target")
+    # Make sure the systemd-network user was set correctly in initrd
+    basic.succeed("[ $(stat -c '%U,%G' /run/systemd/netif/links) = systemd-network,systemd-network ]")
+    basic.succeed("ip addr show >&2")
+    basic.succeed("ip route show >&2")
+  '';
+})
diff --git a/nixos/tests/systemd-initrd-simple.nix b/nixos/tests/systemd-initrd-simple.nix
index f7f4863d17e35..a6a22e9d48e06 100644
--- a/nixos/tests/systemd-initrd-simple.nix
+++ b/nixos/tests/systemd-initrd-simple.nix
@@ -27,6 +27,8 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
         machine.succeed("[ -e /dev/pts/ptmx ]") # /dev/pts
         machine.succeed("[ -e /run/keys ]") # /run/keys
 
+    with subtest("groups work"):
+        machine.fail("journalctl -b 0 | grep 'systemd-udevd.*Unknown group.*ignoring'")
 
     with subtest("growfs works"):
         oldAvail = machine.succeed("df --output=avail / | sed 1d")
diff --git a/nixos/tests/systemd-initrd-swraid.nix b/nixos/tests/systemd-initrd-swraid.nix
index 28a0fb3192aed..d201ba99a2042 100644
--- a/nixos/tests/systemd-initrd-swraid.nix
+++ b/nixos/tests/systemd-initrd-swraid.nix
@@ -20,18 +20,18 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
       services.swraid = {
         enable = true;
         mdadmConf = ''
-          ARRAY /dev/md0 devices=/dev/vdc,/dev/vdd
+          ARRAY /dev/md0 devices=/dev/vdb,/dev/vdc
         '';
       };
       kernelModules = [ "raid0" ];
     };
 
-    specialisation.boot-swraid.configuration.virtualisation.bootDevice = "/dev/disk/by-label/testraid";
+    specialisation.boot-swraid.configuration.virtualisation.rootDevice = "/dev/disk/by-label/testraid";
   };
 
   testScript = ''
     # Create RAID
-    machine.succeed("mdadm --create --force /dev/md0 -n 2 --level=raid0 /dev/vdc /dev/vdd")
+    machine.succeed("mdadm --create --force /dev/md0 -n 2 --level=raid0 /dev/vdb /dev/vdc")
     machine.succeed("mkfs.ext4 -L testraid /dev/md0")
     machine.succeed("mkdir -p /mnt && mount /dev/md0 /mnt && echo hello > /mnt/test && umount /mnt")
 
diff --git a/nixos/tests/systemd-networkd-vrf.nix b/nixos/tests/systemd-networkd-vrf.nix
index 3c18f788e927d..d4227526a30d4 100644
--- a/nixos/tests/systemd-networkd-vrf.nix
+++ b/nixos/tests/systemd-networkd-vrf.nix
@@ -1,5 +1,26 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: let
   inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
+
+  mkNode = vlan: id: {
+    virtualisation.vlans = [ vlan ];
+    networking = {
+      useDHCP = false;
+      useNetworkd = true;
+    };
+
+    systemd.network = {
+      enable = true;
+
+      networks."10-eth${toString vlan}" = {
+        matchConfig.Name = "eth${toString vlan}";
+        linkConfig.RequiredForOnline = "no";
+        networkConfig = {
+          Address = "192.168.${toString vlan}.${toString id}/24";
+          IPForward = "yes";
+        };
+      };
+    };
+  };
 in {
   name = "systemd-networkd-vrf";
   meta.maintainers = with lib.maintainers; [ ma27 ];
@@ -54,7 +75,7 @@ in {
           linkConfig.RequiredForOnline = "no";
           networkConfig = {
             VRF = "vrf1";
-            Address = "192.168.1.1";
+            Address = "192.168.1.1/24";
             IPForward = "yes";
           };
         };
@@ -63,78 +84,23 @@ in {
           linkConfig.RequiredForOnline = "no";
           networkConfig = {
             VRF = "vrf2";
-            Address = "192.168.2.1";
-            IPForward = "yes";
-          };
-        };
-      };
-    };
-
-    node1 = { pkgs, ... }: {
-      virtualisation.vlans = [ 1 ];
-      networking = {
-        useDHCP = false;
-        useNetworkd = true;
-      };
-
-      services.openssh.enable = true;
-      users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
-
-      systemd.network = {
-        enable = true;
-
-        networks."10-eth1" = {
-          matchConfig.Name = "eth1";
-          linkConfig.RequiredForOnline = "no";
-          networkConfig = {
-            Address = "192.168.1.2";
-            IPForward = "yes";
-          };
-        };
-      };
-    };
-
-    node2 = { pkgs, ... }: {
-      virtualisation.vlans = [ 2 ];
-      networking = {
-        useDHCP = false;
-        useNetworkd = true;
-      };
-
-      systemd.network = {
-        enable = true;
-
-        networks."10-eth2" = {
-          matchConfig.Name = "eth2";
-          linkConfig.RequiredForOnline = "no";
-          networkConfig = {
-            Address = "192.168.2.3";
+            Address = "192.168.2.1/24";
             IPForward = "yes";
           };
         };
       };
     };
 
-    node3 = { pkgs, ... }: {
-      virtualisation.vlans = [ 2 ];
-      networking = {
-        useDHCP = false;
-        useNetworkd = true;
-      };
-
-      systemd.network = {
-        enable = true;
+    node1 = lib.mkMerge [
+      (mkNode 1 2)
+      {
+        services.openssh.enable = true;
+        users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
+      }
+    ];
 
-        networks."10-eth2" = {
-          matchConfig.Name = "eth2";
-          linkConfig.RequiredForOnline = "no";
-          networkConfig = {
-            Address = "192.168.2.4";
-            IPForward = "yes";
-          };
-        };
-      };
-    };
+    node2 = mkNode 2 3;
+    node3 = mkNode 2 4;
   };
 
   testScript = ''
@@ -159,22 +125,6 @@ in {
     node2.wait_for_unit("network.target")
     node3.wait_for_unit("network.target")
 
-    client_ipv4_table = """
-    192.168.1.2 dev vrf1 proto static metric 100\x20
-    192.168.2.3 dev vrf2 proto static metric 100
-    """.strip()
-    vrf1_table = """
-    192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1\x20
-    local 192.168.1.1 dev eth1 proto kernel scope host src 192.168.1.1\x20
-    broadcast 192.168.1.255 dev eth1 proto kernel scope link src 192.168.1.1
-    """.strip()
-    vrf2_table = """
-    192.168.2.0/24 dev eth2 proto kernel scope link src 192.168.2.1\x20
-    local 192.168.2.1 dev eth2 proto kernel scope host src 192.168.2.1\x20
-    broadcast 192.168.2.255 dev eth2 proto kernel scope link src 192.168.2.1
-    """.strip()
-    # editorconfig-checker-enable
-
     # Check that networkd properly configures the main routing table
     # and the routing tables for the VRF.
     with subtest("check vrf routing tables"):
diff --git a/nixos/tests/systemd-repart.nix b/nixos/tests/systemd-repart.nix
index 36de5d988fdb1..b1d19c2b7cc1d 100644
--- a/nixos/tests/systemd-repart.nix
+++ b/nixos/tests/systemd-repart.nix
@@ -21,7 +21,7 @@ let
     shutil.copyfile("${machine.system.build.diskImage}/nixos.img", tmp_disk_image.name)
 
     subprocess.run([
-      "${pkgs.qemu}/bin/qemu-img",
+      "${machine.config.virtualisation.qemu.package}/bin/qemu-img",
       "resize",
       "-f",
       "raw",
diff --git a/nixos/tests/vault-agent.nix b/nixos/tests/vault-agent.nix
new file mode 100644
index 0000000000000..dc86c829b67af
--- /dev/null
+++ b/nixos/tests/vault-agent.nix
@@ -0,0 +1,52 @@
+import ./make-test-python.nix ({ pkgs, ... }: {
+  name = "vault-agent";
+
+  nodes.machine = { config, pkgs, ... }: {
+    services.vault-agent.instances.example.settings = {
+      vault.address = config.environment.variables.VAULT_ADDR;
+
+      auto_auth = [{
+        method = [{
+          type = "token_file";
+          config.token_file_path = pkgs.writeText "vault-token" config.environment.variables.VAULT_TOKEN;
+        }];
+      }];
+
+      template = [{
+        contents = ''
+          {{- with secret "secret/example" }}
+          {{ .Data.data.key }}"
+          {{- end }}
+        '';
+        perms = "0600";
+        destination = "/example";
+      }];
+    };
+
+    services.vault = {
+      enable = true;
+      dev = true;
+      devRootTokenID = config.environment.variables.VAULT_TOKEN;
+    };
+
+    environment = {
+      systemPackages = [ pkgs.vault ];
+      variables = {
+        VAULT_ADDR = "http://localhost:8200";
+        VAULT_TOKEN = "root";
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("vault.service")
+    machine.wait_for_open_port(8200)
+
+    machine.wait_until_succeeds('vault kv put secret/example key=example')
+
+    machine.wait_for_unit("vault-agent-example.service")
+
+    machine.wait_for_file("/example")
+    machine.succeed('grep "example" /example')
+  '';
+})
diff --git a/nixos/tests/vector.nix b/nixos/tests/vector.nix
index 9309f6a14dd78..a55eb4e012c5b 100644
--- a/nixos/tests/vector.nix
+++ b/nixos/tests/vector.nix
@@ -31,7 +31,7 @@ with pkgs.lib;
     # ensure vector is forwarding the messages appropriately
     testScript = ''
       machine.wait_for_unit("vector.service")
-      machine.succeed("test -f /var/lib/vector/logs.log")
+      machine.wait_for_file("/var/lib/vector/logs.log")
     '';
   };
 }
diff --git a/nixos/tests/vikunja.nix b/nixos/tests/vikunja.nix
index 2f6c4c1f46617..2660aa9767ca7 100644
--- a/nixos/tests/vikunja.nix
+++ b/nixos/tests/vikunja.nix
@@ -26,6 +26,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         };
         frontendScheme = "http";
         frontendHostname = "localhost";
+        port = 9090;
       };
       services.postgresql = {
         enable = true;
@@ -52,8 +53,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       vikunjaSqlite.succeed("curl --fail http://localhost")
 
       vikunjaPostgresql.wait_for_unit("vikunja-api.service")
-      vikunjaPostgresql.wait_for_open_port(3456)
-      vikunjaPostgresql.succeed("curl --fail http://localhost:3456/api/v1/info")
+      vikunjaPostgresql.wait_for_open_port(9090)
+      vikunjaPostgresql.succeed("curl --fail http://localhost:9090/api/v1/info")
 
       vikunjaPostgresql.wait_for_unit("nginx.service")
       vikunjaPostgresql.wait_for_open_port(80)
diff --git a/nixos/tests/web-apps/monica.nix b/nixos/tests/web-apps/monica.nix
new file mode 100644
index 0000000000000..29f5cb85bb13a
--- /dev/null
+++ b/nixos/tests/web-apps/monica.nix
@@ -0,0 +1,33 @@
+import ../make-test-python.nix ({pkgs, ...}:
+let
+  cert = pkgs.runCommand "selfSignedCerts" { nativeBuildInputs = [ pkgs.openssl ]; } ''
+    openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -nodes -subj '/CN=localhost' -days 36500
+    mkdir -p $out
+    cp key.pem cert.pem $out
+  '';
+in
+{
+  name = "monica";
+
+  nodes = {
+    machine = {pkgs, ...}: {
+      services.monica = {
+        enable = true;
+        hostname = "localhost";
+        appKeyFile = "${pkgs.writeText "keyfile" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}";
+        nginx = {
+          forceSSL = true;
+          sslCertificate = "${cert}/cert.pem";
+          sslCertificateKey = "${cert}/key.pem";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    start_all()
+    machine.wait_for_unit("monica-setup.service")
+    machine.wait_for_open_port(443)
+    machine.succeed("curl -k --fail https://localhost", timeout=10)
+  '';
+})
diff --git a/nixos/tests/web-apps/pixelfed/default.nix b/nixos/tests/web-apps/pixelfed/default.nix
new file mode 100644
index 0000000000000..4464ebe434865
--- /dev/null
+++ b/nixos/tests/web-apps/pixelfed/default.nix
@@ -0,0 +1,8 @@
+{ system ? builtins.currentSystem, handleTestOn }:
+let
+  supportedSystems = [ "x86_64-linux" "i686-linux" ];
+
+in
+{
+  standard = handleTestOn supportedSystems ./standard.nix { inherit system; };
+}
diff --git a/nixos/tests/web-apps/pixelfed/standard.nix b/nixos/tests/web-apps/pixelfed/standard.nix
new file mode 100644
index 0000000000000..9260e27af960d
--- /dev/null
+++ b/nixos/tests/web-apps/pixelfed/standard.nix
@@ -0,0 +1,38 @@
+import ../../make-test-python.nix ({pkgs, ...}:
+{
+  name = "pixelfed-standard";
+  meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+
+  nodes = {
+    server = { pkgs, ... }: {
+      services.pixelfed = {
+        enable = true;
+        domain = "pixelfed.local";
+        # Configure NGINX.
+        nginx = {};
+        secretFile = (pkgs.writeText "secrets.env" ''
+          # Snakeoil secret, can be any random 32-chars secret via CSPRNG.
+          APP_KEY=adKK9EcY8Hcj3PLU7rzG9rJ6KKTOtYfA
+        '');
+        settings."FORCE_HTTPS_URLS" = false;
+      };
+    };
+  };
+
+  testScript = ''
+    # Wait for Pixelfed PHP pool
+    server.wait_for_unit("phpfpm-pixelfed.service")
+    # Wait for NGINX
+    server.wait_for_unit("nginx.service")
+    # Wait for HTTP port
+    server.wait_for_open_port(80)
+    # Access the homepage.
+    server.succeed("curl -H 'Host: pixelfed.local' http://localhost")
+    # Create an account
+    server.succeed("pixelfed-manage user:create --name=test --username=test --email=test@test.com --password=test")
+    # Create a OAuth token.
+    # TODO: figure out how to use it to send a image/toot
+    # server.succeed("pixelfed-manage passport:client --personal")
+    # server.succeed("curl -H 'Host: pixefed.local' -H 'Accept: application/json' -H 'Authorization: Bearer secret' -F'status'='test' http://localhost/api/v1/statuses")
+  '';
+})
diff --git a/nixos/tests/web-apps/snipe-it.nix b/nixos/tests/web-apps/snipe-it.nix
new file mode 100644
index 0000000000000..123d7742056ba
--- /dev/null
+++ b/nixos/tests/web-apps/snipe-it.nix
@@ -0,0 +1,101 @@
+/*
+Snipe-IT NixOS test
+
+It covers the following scenario:
+- Installation
+- Backup and restore
+
+Scenarios NOT covered by this test (but perhaps in the future):
+- Sending and receiving emails
+*/
+{ pkgs, ... }: let
+  siteName = "NixOS Snipe-IT Test Instance";
+in {
+  name = "snipe-it";
+
+  meta.maintainers = with pkgs.lib.maintainers; [ yayayayaka ];
+
+  nodes = {
+    snipeit = { ... }: {
+      services.snipe-it = {
+        enable = true;
+        appKeyFile = toString (pkgs.writeText "snipe-it-app-key" "uTqGUN5GUmUrh/zSAYmhyzRk62pnpXICyXv9eeITI8k=");
+        hostName = "localhost";
+        database.createLocally = true;
+        mail = {
+          driver = "smtp";
+          encryption = "tls";
+          host = "localhost";
+          port = 1025;
+          from.name = "Snipe-IT NixOS test";
+          from.address = "snipe-it@localhost";
+          replyTo.address = "snipe-it@localhost";
+          user = "snipe-it@localhost";
+          passwordFile = toString (pkgs.writeText "snipe-it-mail-pass" "a-secure-mail-password");
+        };
+      };
+    };
+  };
+
+  testScript = { nodes }: let
+    backupPath = "${nodes.snipeit.services.snipe-it.dataDir}/storage/app/backups";
+
+    # Snipe-IT has been installed successfully if the site name shows up on the login page
+    checkLoginPage = { shouldSucceed ? true }: ''
+      snipeit.${if shouldSucceed then "succeed" else "fail"}("""curl http://localhost/login | grep '${siteName}'""")
+    '';
+  in ''
+    start_all()
+
+    snipeit.wait_for_unit("nginx.service")
+    snipeit.wait_for_unit("snipe-it-setup.service")
+
+    # Create an admin user
+    snipeit.succeed(
+        """
+        snipe-it snipeit:create-admin \
+            --username="admin" \
+            --email="janedoe@localhost" \
+            --password="extremesecurepassword" \
+            --first_name="Jane" \
+            --last_name="Doe"
+        """
+    )
+
+    with subtest("Circumvent the pre-flight setup by just writing some settings into the database ourself"):
+        snipeit.succeed(
+            """
+            mysql -D ${nodes.snipeit.services.snipe-it.database.name} -e "INSERT INTO settings (id, user_id, site_name) VALUES ('1', '1', '${siteName}');"
+            """
+        )
+
+        # Usually these are generated during the pre-flight setup
+        snipeit.succeed("snipe-it passport:keys")
+
+
+    # Login page should now contain the configured site name
+    ${checkLoginPage {}}
+
+    with subtest("Test Backup and restore"):
+        snipeit.succeed("snipe-it snipeit:backup")
+
+        # One zip file should have been created
+        snipeit.succeed("""[ "$(ls -1 "${backupPath}" | wc -l)" -eq 1 ]""")
+
+        # Purge the state
+        snipeit.succeed("snipe-it migrate:fresh --force")
+
+        # Login page should disappear
+        ${checkLoginPage { shouldSucceed = false; }}
+
+        # Restore the state
+        snipeit.succeed(
+            """
+            snipe-it snipeit:restore --force $(find "${backupPath}/" -type f -name "*.zip")
+            """
+        )
+
+        # Login page should be back again
+        ${checkLoginPage {}}
+  '';
+}
diff --git a/nixos/tests/web-servers/stargazer.nix b/nixos/tests/web-servers/stargazer.nix
new file mode 100644
index 0000000000000..c522cfee5dbc0
--- /dev/null
+++ b/nixos/tests/web-servers/stargazer.nix
@@ -0,0 +1,31 @@
+{ pkgs, lib, ... }:
+{
+  name = "stargazer";
+  meta = with lib.maintainers; { maintainers = [ gaykitty ]; };
+
+  nodes = {
+    geminiserver = { pkgs, ... }: {
+      services.stargazer = {
+        enable = true;
+        routes = [
+          {
+            route = "localhost";
+            root = toString (pkgs.writeTextDir "index.gmi" ''
+              # Hello NixOS!
+            '');
+          }
+        ];
+      };
+    };
+  };
+
+  testScript = { nodes, ... }: ''
+    geminiserver.wait_for_unit("stargazer")
+    geminiserver.wait_for_open_port(1965)
+
+    with subtest("check is serving over gemini"):
+      response = geminiserver.succeed("${pkgs.gmni}/bin/gmni -j once -i -N gemini://localhost:1965")
+      print(response)
+      assert "Hello NixOS!" in response
+  '';
+}
diff --git a/nixos/tests/wiki-js.nix b/nixos/tests/wiki-js.nix
index c3541be5d8b52..fd054a9c59093 100644
--- a/nixos/tests/wiki-js.nix
+++ b/nixos/tests/wiki-js.nix
@@ -5,7 +5,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
   };
 
   nodes.machine = { pkgs, ... }: {
-    virtualisation.memorySize = 2048;
+    virtualisation.memorySize = 2047;
     services.wiki-js = {
       enable = true;
       settings.db.host = "/run/postgresql";
diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix
index 6a460dbce3547..4e322774fef59 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_1" "6_2"
   ];
 
   testScript = ''
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index bcb9d9bcfd60d..8e52e00657456 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -80,6 +80,11 @@ let
             fsType = "zfs";
             options = [ "noauto" ];
           };
+          virtualisation.fileSystems."/manual/httpkey" = {
+            device = "manual/httpkey";
+            fsType = "zfs";
+            options = [ "noauto" ];
+          };
         };
 
         specialisation.forcepool.configuration = {
@@ -92,21 +97,34 @@ let
             options = [ "noauto" ];
           };
         };
+
+        services.nginx = {
+          enable = true;
+          virtualHosts = {
+            localhost = {
+              locations = {
+                "/zfskey" = {
+                  return = ''200 "httpkeyabc"'';
+                };
+              };
+            };
+          };
+        };
       };
 
       testScript = ''
         machine.wait_for_unit("multi-user.target")
         machine.succeed(
             "zpool status",
+            "parted --script /dev/vdb mklabel msdos",
+            "parted --script /dev/vdb -- mkpart primary 1024M -1s",
             "parted --script /dev/vdc mklabel msdos",
             "parted --script /dev/vdc -- mkpart primary 1024M -1s",
-            "parted --script /dev/vdd mklabel msdos",
-            "parted --script /dev/vdd -- mkpart primary 1024M -1s",
         )
 
         with subtest("sharesmb works"):
             machine.succeed(
-                "zpool create rpool /dev/vdc1",
+                "zpool create rpool /dev/vdb1",
                 "zfs create -o mountpoint=legacy rpool/root",
                 # shared datasets cannot have legacy mountpoint
                 "zfs create rpool/shared_smb",
@@ -126,10 +144,12 @@ let
         with subtest("encryption works"):
             machine.succeed(
                 'echo password | zpool create -O mountpoint=legacy '
-                + "-O encryption=aes-256-gcm -O keyformat=passphrase automatic /dev/vdc1",
-                "zpool create -O mountpoint=legacy manual /dev/vdd1",
+                + "-O encryption=aes-256-gcm -O keyformat=passphrase automatic /dev/vdb1",
+                "zpool create -O mountpoint=legacy manual /dev/vdc1",
                 "echo otherpass | zfs create "
                 + "-o encryption=aes-256-gcm -o keyformat=passphrase manual/encrypted",
+                "zfs create -o encryption=aes-256-gcm -o keyformat=passphrase "
+                + "-o keylocation=http://localhost/zfskey manual/httpkey",
                 "bootctl set-default nixos-generation-1-specialisation-encryption.conf",
                 "sync",
                 "zpool export automatic",
@@ -141,10 +161,12 @@ let
             machine.send_console("password\n")
             machine.wait_for_unit("multi-user.target")
             machine.succeed(
-                "zfs get keystatus manual/encrypted | grep unavailable",
+                "zfs get -Ho value keystatus manual/encrypted | grep -Fx unavailable",
                 "echo otherpass | zfs load-key manual/encrypted",
                 "systemctl start manual-encrypted.mount",
-                "umount /automatic /manual/encrypted /manual",
+                "zfs load-key manual/httpkey",
+                "systemctl start manual-httpkey.mount",
+                "umount /automatic /manual/encrypted /manual/httpkey /manual",
                 "zpool destroy automatic",
                 "zpool destroy manual",
             )
@@ -153,7 +175,7 @@ let
             machine.succeed(
                 "rm /etc/hostid",
                 "zgenhostid deadcafe",
-                "zpool create forcepool /dev/vdc1 -O mountpoint=legacy",
+                "zpool create forcepool /dev/vdb1 -O mountpoint=legacy",
                 "bootctl set-default nixos-generation-1-specialisation-forcepool.conf",
                 "rm /etc/hostid",
                 "sync",