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/configuration/luks-file-systems.section.md2
-rw-r--r--nixos/doc/manual/configuration/profiles/headless.section.md3
-rw-r--r--nixos/doc/manual/configuration/profiles/minimal.section.md3
-rw-r--r--nixos/doc/manual/development/unit-handling.section.md2
-rw-r--r--nixos/doc/manual/installation/installing-from-other-distro.section.md2
-rw-r--r--nixos/doc/manual/installation/installing.chapter.md4
-rw-r--r--nixos/doc/manual/release-notes/rl-2205.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2311.section.md2
-rw-r--r--nixos/doc/manual/release-notes/rl-2405.section.md10
-rw-r--r--nixos/doc/manual/release-notes/rl-2411.section.md217
-rw-r--r--nixos/doc/manual/shell.nix19
-rw-r--r--nixos/lib/make-disk-image.nix64
-rw-r--r--nixos/lib/make-options-doc/default.nix2
-rw-r--r--nixos/lib/systemd-lib.nix27
-rw-r--r--nixos/lib/systemd-types.nix71
-rw-r--r--nixos/lib/test-driver/shell.nix6
-rw-r--r--nixos/lib/testing/network.nix32
-rw-r--r--nixos/lib/utils.nix91
-rw-r--r--nixos/maintainers/scripts/azure-new/shell.nix11
-rw-r--r--nixos/maintainers/scripts/ec2/amazon-image.nix8
-rw-r--r--nixos/maintainers/scripts/incus/incus-container-image-inner.nix34
-rw-r--r--nixos/maintainers/scripts/incus/incus-container-image.nix47
-rw-r--r--nixos/maintainers/scripts/incus/incus-virtual-machine-image-inner.nix34
-rw-r--r--nixos/maintainers/scripts/incus/incus-virtual-machine-image.nix48
-rw-r--r--nixos/maintainers/scripts/incus/nix.tpl12
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix2
-rw-r--r--nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix2
-rw-r--r--nixos/modules/config/fonts/ghostscript.nix2
-rw-r--r--nixos/modules/config/ldap.nix36
-rw-r--r--nixos/modules/config/nix-channel.nix6
-rw-r--r--nixos/modules/config/nix-channel/activation-check.sh21
-rw-r--r--nixos/modules/config/nix-channel/test.nix19
-rw-r--r--nixos/modules/config/nix.nix2
-rw-r--r--nixos/modules/config/no-x-libs.nix1
-rw-r--r--nixos/modules/config/pulseaudio.nix10
-rw-r--r--nixos/modules/config/shells-environment.nix6
-rw-r--r--nixos/modules/config/stevenblack.nix49
-rw-r--r--nixos/modules/config/swap.nix45
-rw-r--r--nixos/modules/config/system-path.nix2
-rw-r--r--nixos/modules/config/update-users-groups.pl2
-rw-r--r--nixos/modules/config/users-groups.nix61
-rw-r--r--nixos/modules/config/xdg/portal.nix27
-rw-r--r--nixos/modules/hardware/all-firmware.nix1
-rw-r--r--nixos/modules/hardware/ckb-next.nix2
-rw-r--r--nixos/modules/hardware/decklink.nix18
-rw-r--r--nixos/modules/hardware/network/eg25-manager.nix27
-rw-r--r--nixos/modules/hardware/raid/hpsa.nix2
-rw-r--r--nixos/modules/hardware/video/nvidia.nix38
-rw-r--r--nixos/modules/hardware/video/webcam/ipu6.nix6
-rw-r--r--nixos/modules/i18n/input-method/default.md18
-rw-r--r--nixos/modules/i18n/input-method/default.nix20
-rw-r--r--nixos/modules/i18n/input-method/fcitx5.nix6
-rw-r--r--nixos/modules/i18n/input-method/hime.nix6
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix8
-rw-r--r--nixos/modules/i18n/input-method/kime.nix5
-rw-r--r--nixos/modules/i18n/input-method/nabi.nix5
-rw-r--r--nixos/modules/i18n/input-method/uim.nix5
-rw-r--r--nixos/modules/image/repart-image.nix2
-rw-r--r--nixos/modules/installer/netboot/netboot.nix4
-rw-r--r--nixos/modules/installer/sd-card/sd-image-aarch64.nix6
-rw-r--r--nixos/modules/installer/tools/manpages/nixos-install.828
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix10
-rwxr-xr-xnixos/modules/installer/tools/nixos-install.sh68
-rw-r--r--nixos/modules/misc/ids.nix8
-rw-r--r--nixos/modules/misc/locate.nix5
-rw-r--r--nixos/modules/module-list.nix45
-rw-r--r--nixos/modules/profiles/docker-container.nix2
-rw-r--r--nixos/modules/profiles/installation-device.nix12
-rw-r--r--nixos/modules/profiles/macos-builder.nix2
-rw-r--r--nixos/modules/profiles/perlless.nix11
-rw-r--r--nixos/modules/profiles/qemu-guest.nix12
-rw-r--r--nixos/modules/programs/appimage.nix40
-rw-r--r--nixos/modules/programs/bash/bash.nix2
-rw-r--r--nixos/modules/programs/benchexec.nix9
-rw-r--r--nixos/modules/programs/chromium.nix9
-rw-r--r--nixos/modules/programs/direnv.nix79
-rw-r--r--nixos/modules/programs/dmrconfig.nix2
-rw-r--r--nixos/modules/programs/envision.nix51
-rw-r--r--nixos/modules/programs/firefox.nix5
-rw-r--r--nixos/modules/programs/fish.nix4
-rw-r--r--nixos/modules/programs/gpu-screen-recorder.nix40
-rw-r--r--nixos/modules/programs/immersed-vr.nix34
-rw-r--r--nixos/modules/programs/kde-pim.nix30
-rw-r--r--nixos/modules/programs/localsend.nix26
-rw-r--r--nixos/modules/programs/miriway.nix2
-rw-r--r--nixos/modules/programs/nano.nix1
-rw-r--r--nixos/modules/programs/neovim.nix4
-rw-r--r--nixos/modules/programs/nix-required-mounts.nix4
-rw-r--r--nixos/modules/programs/qgroundcontrol.nix53
-rw-r--r--nixos/modules/programs/regreet.nix87
-rw-r--r--nixos/modules/programs/ssh.nix2
-rw-r--r--nixos/modules/programs/tmux.nix4
-rw-r--r--nixos/modules/programs/tsm-client.nix6
-rw-r--r--nixos/modules/programs/vim.nix38
-rw-r--r--nixos/modules/programs/wayland/miracle-wm.nix43
-rw-r--r--nixos/modules/programs/wayland/wayfire.nix63
-rw-r--r--nixos/modules/programs/wayland/wayland-session.nix2
-rw-r--r--nixos/modules/programs/ydotool.nix1
-rw-r--r--nixos/modules/programs/zsh/zsh.nix2
-rw-r--r--nixos/modules/rename.nix7
-rw-r--r--nixos/modules/security/apparmor/profiles.nix2
-rw-r--r--nixos/modules/security/pam.nix277
-rw-r--r--nixos/modules/security/wrappers/default.nix16
-rw-r--r--nixos/modules/services/accessibility/speechd.nix32
-rw-r--r--nixos/modules/services/audio/alsa.nix144
-rw-r--r--nixos/modules/services/audio/goxlr-utility.nix25
-rw-r--r--nixos/modules/services/audio/jack.nix4
-rw-r--r--nixos/modules/services/audio/jmusicbot.nix2
-rw-r--r--nixos/modules/services/audio/music-assistant.nix113
-rw-r--r--nixos/modules/services/audio/snapserver.nix1
-rw-r--r--nixos/modules/services/backup/borgmatic.nix57
-rw-r--r--nixos/modules/services/backup/btrbk.nix4
-rw-r--r--nixos/modules/services/backup/duplicity.nix24
-rw-r--r--nixos/modules/services/backup/mysql-backup.nix10
-rw-r--r--nixos/modules/services/backup/restic-rest-server.nix2
-rw-r--r--nixos/modules/services/backup/restic.nix22
-rw-r--r--nixos/modules/services/backup/tsm.nix2
-rw-r--r--nixos/modules/services/blockchain/ethereum/lighthouse.nix2
-rw-r--r--nixos/modules/services/cluster/druid/default.nix296
-rw-r--r--nixos/modules/services/cluster/k3s/default.nix413
-rw-r--r--nixos/modules/services/cluster/kubernetes/apiserver.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/controller-manager.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix6
-rw-r--r--nixos/modules/services/cluster/kubernetes/proxy.nix8
-rw-r--r--nixos/modules/services/cluster/kubernetes/scheduler.nix8
-rw-r--r--nixos/modules/services/cluster/patroni/default.nix36
-rw-r--r--nixos/modules/services/cluster/rke2/default.nix2
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agents.nix2
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix26
-rw-r--r--nixos/modules/services/databases/monetdb.nix2
-rw-r--r--nixos/modules/services/databases/redis.nix9
-rw-r--r--nixos/modules/services/databases/surrealdb.nix2
-rw-r--r--nixos/modules/services/desktop-managers/lomiri.nix23
-rw-r--r--nixos/modules/services/desktop-managers/plasma6.nix4
-rw-r--r--nixos/modules/services/desktops/ayatana-indicators.nix40
-rw-r--r--nixos/modules/services/desktops/espanso.nix2
-rw-r--r--nixos/modules/services/desktops/flatpak.nix10
-rw-r--r--nixos/modules/services/development/livebook.nix12
-rw-r--r--nixos/modules/services/display-managers/default.nix3
-rw-r--r--nixos/modules/services/display-managers/ly.nix147
-rw-r--r--nixos/modules/services/finance/odoo.nix46
-rw-r--r--nixos/modules/services/games/archisteamfarm.nix7
-rw-r--r--nixos/modules/services/games/terraria.nix23
-rw-r--r--nixos/modules/services/hardware/display.md130
-rw-r--r--nixos/modules/services/hardware/display.nix193
-rw-r--r--nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix2
-rw-r--r--nixos/modules/services/hardware/nvidia-container-toolkit/default.nix12
-rw-r--r--nixos/modules/services/hardware/openrgb.nix2
-rw-r--r--nixos/modules/services/hardware/sane.nix4
-rw-r--r--nixos/modules/services/hardware/udev.nix5
-rw-r--r--nixos/modules/services/home-automation/home-assistant.nix7
-rw-r--r--nixos/modules/services/logging/graylog.nix7
-rw-r--r--nixos/modules/services/mail/dovecot.nix2
-rw-r--r--nixos/modules/services/mail/mailman.nix5
-rw-r--r--nixos/modules/services/mail/postfix.nix2
-rw-r--r--nixos/modules/services/mail/protonmail-bridge.nix60
-rw-r--r--nixos/modules/services/mail/roundcube.nix12
-rw-r--r--nixos/modules/services/misc/airsonic.nix2
-rw-r--r--nixos/modules/services/misc/ananicy.nix212
-rw-r--r--nixos/modules/services/misc/blenderfarm.nix141
-rw-r--r--nixos/modules/services/misc/dictd.nix3
-rw-r--r--nixos/modules/services/misc/etebase-server.nix2
-rw-r--r--nixos/modules/services/misc/flaresolverr.nix58
-rw-r--r--nixos/modules/services/misc/forgejo.nix2
-rw-r--r--nixos/modules/services/misc/fstrim.nix2
-rw-r--r--nixos/modules/services/misc/gitlab.nix37
-rw-r--r--nixos/modules/services/misc/gitweb.nix2
-rw-r--r--nixos/modules/services/misc/gollum.nix47
-rw-r--r--nixos/modules/services/misc/gotenberg.nix258
-rw-r--r--nixos/modules/services/misc/graphical-desktop.nix2
-rw-r--r--nixos/modules/services/misc/jackett.nix12
-rw-r--r--nixos/modules/services/misc/jellyseerr.nix7
-rw-r--r--nixos/modules/services/misc/languagetool.nix43
-rw-r--r--nixos/modules/services/misc/mame.nix2
-rw-r--r--nixos/modules/services/misc/ollama.nix206
-rw-r--r--nixos/modules/services/misc/open-webui.nix5
-rw-r--r--nixos/modules/services/misc/private-gpt.nix2
-rw-r--r--nixos/modules/services/misc/radicle.nix358
-rw-r--r--nixos/modules/services/misc/redlib.nix (renamed from nixos/modules/services/misc/libreddit.nix)14
-rw-r--r--nixos/modules/services/misc/rkvm.nix2
-rw-r--r--nixos/modules/services/misc/snapper.nix26
-rw-r--r--nixos/modules/services/misc/sonarr.nix8
-rw-r--r--nixos/modules/services/misc/sssd.nix2
-rw-r--r--nixos/modules/services/misc/xmr-stak.nix89
-rw-r--r--nixos/modules/services/misc/zoneminder.nix7
-rw-r--r--nixos/modules/services/monitoring/librenms.nix127
-rw-r--r--nixos/modules/services/monitoring/nagios.nix4
-rw-r--r--nixos/modules/services/monitoring/netdata.nix3
-rw-r--r--nixos/modules/services/monitoring/opentelemetry-collector.nix47
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix11
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix52
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix17
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/borgmatic.nix34
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/deluge.nix85
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix22
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/pve.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix14
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix30
-rw-r--r--nixos/modules/services/monitoring/prometheus/pushgateway.nix44
-rw-r--r--nixos/modules/services/monitoring/scrutiny.nix5
-rw-r--r--nixos/modules/services/networking/antennas.nix2
-rw-r--r--nixos/modules/services/networking/bee.nix5
-rw-r--r--nixos/modules/services/networking/bind.nix3
-rw-r--r--nixos/modules/services/networking/cgit.nix4
-rw-r--r--nixos/modules/services/networking/cloudflare-dyndns.nix17
-rw-r--r--nixos/modules/services/networking/cloudflare-warp.nix91
-rw-r--r--nixos/modules/services/networking/ddns-updater.nix46
-rw-r--r--nixos/modules/services/networking/deconz.nix3
-rw-r--r--nixos/modules/services/networking/eternal-terminal.nix2
-rw-r--r--nixos/modules/services/networking/firefox-syncserver.nix2
-rw-r--r--nixos/modules/services/networking/hans.nix2
-rw-r--r--nixos/modules/services/networking/hickory-dns.nix (renamed from nixos/modules/services/networking/trust-dns.nix)39
-rw-r--r--nixos/modules/services/networking/i2pd.nix68
-rw-r--r--nixos/modules/services/networking/magic-wormhole-mailbox-server.nix24
-rw-r--r--nixos/modules/services/networking/mosquitto.nix4
-rw-r--r--nixos/modules/services/networking/mxisd.nix137
-rw-r--r--nixos/modules/services/networking/nar-serve.nix15
-rw-r--r--nixos/modules/services/networking/nats.nix9
-rw-r--r--nixos/modules/services/networking/nebula.nix8
-rw-r--r--nixos/modules/services/networking/opengfw.nix414
-rw-r--r--nixos/modules/services/networking/openvpn.nix2
-rw-r--r--nixos/modules/services/networking/pppd.nix2
-rw-r--r--nixos/modules/services/networking/prosody.nix57
-rw-r--r--nixos/modules/services/networking/rathole.nix165
-rw-r--r--nixos/modules/services/networking/realm.nix50
-rw-r--r--nixos/modules/services/networking/resilio.nix2
-rw-r--r--nixos/modules/services/networking/scion/scion-control.nix12
-rw-r--r--nixos/modules/services/networking/scion/scion-daemon.nix10
-rw-r--r--nixos/modules/services/networking/scion/scion-dispatcher.nix5
-rw-r--r--nixos/modules/services/networking/scion/scion-router.nix5
-rw-r--r--nixos/modules/services/networking/scion/scion.nix19
-rw-r--r--nixos/modules/services/networking/smokeping.nix4
-rw-r--r--nixos/modules/services/networking/spiped.nix20
-rw-r--r--nixos/modules/services/networking/wg-quick.nix25
-rw-r--r--nixos/modules/services/networking/wireguard.nix36
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix14
-rw-r--r--nixos/modules/services/networking/wstunnel.nix4
-rw-r--r--nixos/modules/services/networking/wvdial.nix47
-rw-r--r--nixos/modules/services/networking/zeronsd.nix117
-rw-r--r--nixos/modules/services/search/tika.nix98
-rw-r--r--nixos/modules/services/security/authelia.nix48
-rw-r--r--nixos/modules/services/security/clamav.nix10
-rw-r--r--nixos/modules/services/security/hologram-agent.nix2
-rw-r--r--nixos/modules/services/security/shibboleth-sp.nix2
-rw-r--r--nixos/modules/services/security/sks.nix2
-rw-r--r--nixos/modules/services/security/step-ca.nix2
-rw-r--r--nixos/modules/services/security/vaultwarden/default.nix20
-rw-r--r--nixos/modules/services/ttys/kmscon.nix2
-rw-r--r--nixos/modules/services/video/frigate.nix2
-rw-r--r--nixos/modules/services/video/replay-sorcery.nix72
-rw-r--r--nixos/modules/services/web-apps/akkoma.nix6
-rw-r--r--nixos/modules/services/web-apps/cryptpad.nix293
-rw-r--r--nixos/modules/services/web-apps/eintopf.nix92
-rw-r--r--nixos/modules/services/web-apps/freshrss.nix5
-rw-r--r--nixos/modules/services/web-apps/glance.md39
-rw-r--r--nixos/modules/services/web-apps/glance.nix141
-rw-r--r--nixos/modules/services/web-apps/goatcounter.nix80
-rw-r--r--nixos/modules/services/web-apps/gotify-server.nix95
-rw-r--r--nixos/modules/services/web-apps/ifm.nix81
-rw-r--r--nixos/modules/services/web-apps/jitsi-meet.nix14
-rw-r--r--nixos/modules/services/web-apps/mealie.nix1
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix196
-rw-r--r--nixos/modules/services/web-apps/meme-bingo-web.nix6
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix12
-rw-r--r--nixos/modules/services/web-apps/misskey.nix418
-rw-r--r--nixos/modules/services/web-apps/movim.nix4
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix6
-rw-r--r--nixos/modules/services/web-apps/onlyoffice.nix58
-rw-r--r--nixos/modules/services/web-apps/pretix.nix10
-rw-r--r--nixos/modules/services/web-apps/screego.nix96
-rw-r--r--nixos/modules/services/web-apps/sogo.nix8
-rw-r--r--nixos/modules/services/web-apps/stirling-pdf.nix110
-rw-r--r--nixos/modules/services/web-apps/weblate.nix388
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix2
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix252
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix18
-rw-r--r--nixos/modules/services/web-servers/caddy/default.nix8
-rw-r--r--nixos/modules/services/web-servers/fcgiwrap.nix32
-rw-r--r--nixos/modules/services/web-servers/hydron.nix164
-rw-r--r--nixos/modules/services/web-servers/nginx/gitweb.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/budgie.nix30
-rw-r--r--nixos/modules/services/x11/desktop-managers/cinnamon.nix28
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix2
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix5
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix4
-rw-r--r--nixos/modules/services/x11/xserver.nix3
-rw-r--r--nixos/modules/system/activation/activation-script.nix2
-rw-r--r--nixos/modules/system/activation/lib/lib.sh5
-rw-r--r--nixos/modules/system/activation/lib/test.nix36
-rwxr-xr-xnixos/modules/system/activation/lib/test.sh34
-rw-r--r--nixos/modules/system/activation/specialisation.nix19
-rwxr-xr-xnixos/modules/system/activation/switch-to-configuration.pl5
-rw-r--r--nixos/modules/system/boot/binfmt.nix4
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir.nix2
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/boot-counting.md38
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py355
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix52
-rw-r--r--nixos/modules/system/boot/networkd.nix26
-rw-r--r--nixos/modules/system/boot/plymouth.nix2
-rw-r--r--nixos/modules/system/boot/stage-1.nix1
-rw-r--r--nixos/modules/system/boot/systemd.nix37
-rw-r--r--nixos/modules/system/boot/systemd/initrd.nix28
-rw-r--r--nixos/modules/system/boot/systemd/journald.nix1
-rw-r--r--nixos/modules/system/boot/systemd/nspawn.nix3
-rw-r--r--nixos/modules/system/boot/systemd/shutdown.nix10
-rw-r--r--nixos/modules/system/boot/systemd/sysusers.nix215
-rw-r--r--nixos/modules/system/boot/systemd/tmpfiles.nix6
-rw-r--r--nixos/modules/system/etc/etc.nix27
-rw-r--r--nixos/modules/tasks/filesystems/btrfs.nix38
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix5
-rw-r--r--nixos/modules/virtualisation/docker.nix4
-rw-r--r--nixos/modules/virtualisation/ec2-data.nix2
-rw-r--r--nixos/modules/virtualisation/includes-to-excludes.py86
-rw-r--r--nixos/modules/virtualisation/incus-agent.nix41
-rw-r--r--nixos/modules/virtualisation/incus-virtual-machine.nix61
-rw-r--r--nixos/modules/virtualisation/incus.nix7
-rw-r--r--nixos/modules/virtualisation/lxc-container.nix2
-rw-r--r--nixos/modules/virtualisation/lxc-image-metadata.nix4
-rw-r--r--nixos/modules/virtualisation/lxc-instance-common.nix4
-rw-r--r--nixos/modules/virtualisation/lxd-agent.nix4
-rw-r--r--nixos/modules/virtualisation/lxd-virtual-machine.nix4
-rw-r--r--nixos/modules/virtualisation/lxd.nix9
-rw-r--r--nixos/modules/virtualisation/nixos-containers.nix4
-rw-r--r--nixos/modules/virtualisation/oci-containers.nix13
-rw-r--r--nixos/modules/virtualisation/podman/default.nix2
-rw-r--r--nixos/modules/virtualisation/proxmox-image.nix8
-rw-r--r--nixos/modules/virtualisation/proxmox-lxc.nix75
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix192
-rw-r--r--nixos/modules/virtualisation/vagrant-virtualbox-image.nix1
-rw-r--r--nixos/release-small.nix5
-rw-r--r--nixos/release.nix95
-rw-r--r--nixos/tests/acme.nix28
-rw-r--r--nixos/tests/activation/etc-overlay-immutable.nix29
-rw-r--r--nixos/tests/activation/etc-overlay-mutable.nix13
-rw-r--r--nixos/tests/all-tests.nix47
-rw-r--r--nixos/tests/armagetronad.nix4
-rw-r--r--nixos/tests/ayatana-indicators.nix2
-rw-r--r--nixos/tests/bind.nix1
-rw-r--r--nixos/tests/bittorrent.nix2
-rw-r--r--nixos/tests/borgmatic.nix24
-rw-r--r--nixos/tests/bpf.nix7
-rw-r--r--nixos/tests/budgie.nix4
-rw-r--r--nixos/tests/common/acme/client/default.nix2
-rw-r--r--nixos/tests/crabfit.nix2
-rw-r--r--nixos/tests/cryptpad.nix71
-rw-r--r--nixos/tests/darling-dmg.nix34
-rw-r--r--nixos/tests/ddns-updater.nix28
-rw-r--r--nixos/tests/docker-tools-nix-shell.nix95
-rw-r--r--nixos/tests/docker-tools.nix56
-rw-r--r--nixos/tests/domination.nix1
-rw-r--r--nixos/tests/druid/default.nix289
-rw-r--r--nixos/tests/eintopf.nix21
-rw-r--r--nixos/tests/firefox.nix3
-rw-r--r--nixos/tests/firewall.nix2
-rw-r--r--nixos/tests/flaresolverr.nix22
-rw-r--r--nixos/tests/forgejo.nix4
-rw-r--r--nixos/tests/ft2-clone.nix2
-rw-r--r--nixos/tests/gitlab.nix2
-rw-r--r--nixos/tests/gitolite-fcgiwrap.nix4
-rw-r--r--nixos/tests/glance.nix36
-rw-r--r--nixos/tests/goatcounter.nix32
-rw-r--r--nixos/tests/gotenberg.nix26
-rw-r--r--nixos/tests/gotify-server.nix4
-rw-r--r--nixos/tests/graylog.nix17
-rw-r--r--nixos/tests/hadoop/hadoop.nix1
-rw-r--r--nixos/tests/ifm.nix36
-rw-r--r--nixos/tests/incus/container.nix6
-rw-r--r--nixos/tests/incus/incusd-options.nix6
-rw-r--r--nixos/tests/incus/virtual-machine.nix10
-rw-r--r--nixos/tests/installed-tests/flatpak-builder.nix7
-rw-r--r--nixos/tests/installed-tests/ibus.nix5
-rw-r--r--nixos/tests/installed-tests/ostree.nix2
-rw-r--r--nixos/tests/installer.nix190
-rw-r--r--nixos/tests/installer/byAttrNoChannel.nix18
-rw-r--r--nixos/tests/installer/byAttrWithChannel.nix10
-rw-r--r--nixos/tests/ipv6.nix2
-rw-r--r--nixos/tests/iscsi-root.nix6
-rw-r--r--nixos/tests/jackett.nix14
-rw-r--r--nixos/tests/jool.nix18
-rw-r--r--nixos/tests/k3s/airgap-images.nix42
-rw-r--r--nixos/tests/k3s/auto-deploy.nix125
-rw-r--r--nixos/tests/k3s/containerd-config.nix52
-rw-r--r--nixos/tests/k3s/default.nix15
-rw-r--r--nixos/tests/k3s/etcd.nix23
-rw-r--r--nixos/tests/k3s/kubelet-config.nix80
-rw-r--r--nixos/tests/k3s/multi-node.nix28
-rw-r--r--nixos/tests/k3s/single-node.nix24
-rw-r--r--nixos/tests/kernel-generic.nix5
-rw-r--r--nixos/tests/keycloak.nix9
-rw-r--r--nixos/tests/kubernetes/base.nix4
-rw-r--r--nixos/tests/libreddit.nix19
-rw-r--r--nixos/tests/librenms.nix46
-rw-r--r--nixos/tests/livebook-service.nix5
-rw-r--r--nixos/tests/localsend.nix21
-rw-r--r--nixos/tests/login.nix1
-rw-r--r--nixos/tests/lomiri-calculator-app.nix59
-rw-r--r--nixos/tests/lomiri-camera-app.nix135
-rw-r--r--nixos/tests/lomiri-clock-app.nix48
-rw-r--r--nixos/tests/lomiri-filemanager-app.nix2
-rw-r--r--nixos/tests/lomiri-system-settings.nix2
-rw-r--r--nixos/tests/lomiri.nix811
-rw-r--r--nixos/tests/lorri/default.nix6
-rw-r--r--nixos/tests/lxd/container.nix4
-rw-r--r--nixos/tests/lxd/nftables.nix4
-rw-r--r--nixos/tests/lxd/preseed.nix4
-rw-r--r--nixos/tests/lxd/ui.nix6
-rw-r--r--nixos/tests/lxd/virtual-machine.nix4
-rw-r--r--nixos/tests/ly.nix44
-rw-r--r--nixos/tests/mediatomb.nix25
-rw-r--r--nixos/tests/miracle-wm.nix131
-rw-r--r--nixos/tests/misc.nix3
-rw-r--r--nixos/tests/misskey.nix29
-rw-r--r--nixos/tests/morph-browser.nix2
-rw-r--r--nixos/tests/mpd.nix1
-rw-r--r--nixos/tests/music-assistant.nix21
-rw-r--r--nixos/tests/mxisd.nix21
-rw-r--r--nixos/tests/nat.nix24
-rw-r--r--nixos/tests/networking-proxy.nix2
-rw-r--r--nixos/tests/networking/networkmanager.nix2
-rw-r--r--nixos/tests/nixos-rebuild-specialisations.nix33
-rw-r--r--nixos/tests/nvmetcfg.nix2
-rw-r--r--nixos/tests/nzbhydra2.nix2
-rw-r--r--nixos/tests/odoo.nix14
-rw-r--r--nixos/tests/ollama-cuda.nix17
-rw-r--r--nixos/tests/ollama-rocm.nix17
-rw-r--r--nixos/tests/ollama.nix79
-rw-r--r--nixos/tests/pam/pam-u2f.nix16
-rw-r--r--nixos/tests/pantheon.nix3
-rw-r--r--nixos/tests/postgresql-tls-client-cert.nix141
-rw-r--r--nixos/tests/prometheus-exporters.nix48
-rw-r--r--nixos/tests/prometheus/alertmanager.nix4
-rw-r--r--nixos/tests/prometheus/pushgateway.nix2
-rw-r--r--nixos/tests/pt2-clone.nix1
-rw-r--r--nixos/tests/qemu-vm-store.nix71
-rw-r--r--nixos/tests/quake3.nix4
-rw-r--r--nixos/tests/radicle.nix207
-rw-r--r--nixos/tests/rathole.nix89
-rw-r--r--nixos/tests/realm.nix39
-rw-r--r--nixos/tests/redlib.nix4
-rw-r--r--nixos/tests/restic.nix29
-rw-r--r--nixos/tests/rosenpass.nix3
-rw-r--r--nixos/tests/sfxr-qt.nix1
-rw-r--r--nixos/tests/shattered-pixel-dungeon.nix1
-rw-r--r--nixos/tests/slimserver.nix3
-rw-r--r--nixos/tests/sogo.nix2
-rw-r--r--nixos/tests/soju.nix2
-rw-r--r--nixos/tests/step-ca.nix2
-rw-r--r--nixos/tests/systemd-analyze.nix1
-rw-r--r--nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch25
-rw-r--r--nixos/tests/systemd-boot.nix214
-rw-r--r--nixos/tests/systemd-homed.nix4
-rw-r--r--nixos/tests/systemd-networkd-dhcpserver-static-leases.nix2
-rw-r--r--nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix5
-rw-r--r--nixos/tests/systemd-networkd-vrf.nix15
-rw-r--r--nixos/tests/systemd-networkd.nix6
-rw-r--r--nixos/tests/systemd-sysusers-immutable.nix57
-rw-r--r--nixos/tests/systemd-sysusers-mutable.nix39
-rw-r--r--nixos/tests/systemd.nix3
-rw-r--r--nixos/tests/teleport.nix1
-rw-r--r--nixos/tests/teleports.nix48
-rw-r--r--nixos/tests/tika.nix21
-rw-r--r--nixos/tests/user-home-mode.nix8
-rw-r--r--nixos/tests/vaultwarden.nix15
-rw-r--r--nixos/tests/vector/dnstap.nix2
-rw-r--r--nixos/tests/vector/syslog-quickwit.nix1
-rw-r--r--nixos/tests/vscode-remote-ssh.nix2
-rw-r--r--nixos/tests/web-apps/mastodon/default.nix6
-rw-r--r--nixos/tests/web-apps/pixelfed/standard.nix7
-rw-r--r--nixos/tests/web-apps/pretix.nix1
-rw-r--r--nixos/tests/web-apps/weblate.nix104
472 files changed, 13557 insertions, 3501 deletions
diff --git a/nixos/doc/manual/configuration/luks-file-systems.section.md b/nixos/doc/manual/configuration/luks-file-systems.section.md
index 4d2f625073d4a..b20957b40b894 100644
--- a/nixos/doc/manual/configuration/luks-file-systems.section.md
+++ b/nixos/doc/manual/configuration/luks-file-systems.section.md
@@ -90,7 +90,7 @@ as [Trezor](https://trezor.io/).
 
 ### systemd Stage 1 {#sec-luks-file-systems-fido2-systemd}
 
-If systemd stage 1 is enabled, it handles unlocking of LUKS-enrypted volumes
+If systemd stage 1 is enabled, it handles unlocking of LUKS-encrypted volumes
 during boot. The following example enables systemd stage1 and adds support for
 unlocking the existing LUKS2 volume `root` using any enrolled FIDO2 compatible
 tokens.
diff --git a/nixos/doc/manual/configuration/profiles/headless.section.md b/nixos/doc/manual/configuration/profiles/headless.section.md
index d185a9a774b71..6b84bb7d5cc97 100644
--- a/nixos/doc/manual/configuration/profiles/headless.section.md
+++ b/nixos/doc/manual/configuration/profiles/headless.section.md
@@ -2,8 +2,7 @@
 
 Common configuration for headless machines (e.g., Amazon EC2 instances).
 
-Disables [sound](#opt-sound.enable),
-[vesa](#opt-boot.vesa), serial consoles,
+Disables [vesa](#opt-boot.vesa), serial consoles,
 [emergency mode](#opt-systemd.enableEmergencyMode),
 [grub splash images](#opt-boot.loader.grub.splashImage)
 and configures the kernel to reboot automatically on panic.
diff --git a/nixos/doc/manual/configuration/profiles/minimal.section.md b/nixos/doc/manual/configuration/profiles/minimal.section.md
index 02a3b65ae422a..76d9585a0bd3c 100644
--- a/nixos/doc/manual/configuration/profiles/minimal.section.md
+++ b/nixos/doc/manual/configuration/profiles/minimal.section.md
@@ -5,5 +5,4 @@ graphical stuff. It's a very short file that enables
 [noXlibs](#opt-environment.noXlibs), sets
 [](#opt-i18n.supportedLocales) to
 only support the user-selected locale,
-[disables packages' documentation](#opt-documentation.enable),
-and [disables sound](#opt-sound.enable).
+and [disables packages' documentation](#opt-documentation.enable).
diff --git a/nixos/doc/manual/development/unit-handling.section.md b/nixos/doc/manual/development/unit-handling.section.md
index 1f6a30d6ef343..deb120b9fadda 100644
--- a/nixos/doc/manual/development/unit-handling.section.md
+++ b/nixos/doc/manual/development/unit-handling.section.md
@@ -75,7 +75,7 @@ units".
 
 "Normal" systemd units, by default, are ordered AFTER `sysinit.target`. In
 other words, these "normal" units expect all services ordered before
-`sysinit.target` to have finished without explicity declaring this dependency
+`sysinit.target` to have finished without explicitly declaring this dependency
 relationship for each dependency. See the [systemd
 bootup](https://www.freedesktop.org/software/systemd/man/latest/bootup.html)
 for more details on the bootup process.
diff --git a/nixos/doc/manual/installation/installing-from-other-distro.section.md b/nixos/doc/manual/installation/installing-from-other-distro.section.md
index 38f0e5301472b..2d9818e6805c5 100644
--- a/nixos/doc/manual/installation/installing-from-other-distro.section.md
+++ b/nixos/doc/manual/installation/installing-from-other-distro.section.md
@@ -125,7 +125,7 @@ The first steps to all these are the same:
     :::
 
     ```ShellSession
-    $ sudo PATH="$PATH" NIX_PATH="$NIX_PATH" `which nixos-install` --root /mnt
+    $ sudo PATH="$PATH" `which nixos-install` --root /mnt
     ```
 
     Again, please refer to the `nixos-install` step in
diff --git a/nixos/doc/manual/installation/installing.chapter.md b/nixos/doc/manual/installation/installing.chapter.md
index b6db40878ba76..9f3ff2ac6bac3 100644
--- a/nixos/doc/manual/installation/installing.chapter.md
+++ b/nixos/doc/manual/installation/installing.chapter.md
@@ -174,8 +174,6 @@ commands:
 OK
 > set_network 0 psk "mypassword"
 OK
-> set_network 0 key_mgmt WPA-PSK
-OK
 > enable_network 0
 OK
 ```
@@ -191,8 +189,6 @@ OK
 OK
 > set_network 0 password "mypassword"
 OK
-> set_network 0 key_mgmt WPA-EAP
-OK
 > enable_network 0
 OK
 ```
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index dad45f12373e6..e729fdcbb1397 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -824,7 +824,7 @@ In addition to numerous new and upgraded packages, this release has the followin
   Configurations using this default will print a warning when rebuilt.
 
 - `security.acme` certificates will now correctly check for CA
-  revokation before reaching their minimum age.
+  revocation before reaching their minimum age.
 
 - Removing domains from `security.acme.certs._name_.extraDomainNames`
   will now correctly remove those domains during rebuild/renew.
diff --git a/nixos/doc/manual/release-notes/rl-2311.section.md b/nixos/doc/manual/release-notes/rl-2311.section.md
index d837e0ff68b7c..0fd5f802d2492 100644
--- a/nixos/doc/manual/release-notes/rl-2311.section.md
+++ b/nixos/doc/manual/release-notes/rl-2311.section.md
@@ -1017,7 +1017,7 @@ Make sure to also check the many updates in the [Nixpkgs library](#sec-release-2
 
 - [trust-dns](https://trust-dns.org/), a Rust based DNS server built to be safe
   and secure from the ground up. Available as
-  [services.trust-dns](#opt-services.trust-dns.enable).
+  `services.trust-dns`.
 
 - [osquery](https://www.osquery.io/), a SQL powered operating system
   instrumentation, monitoring, and analytics. Available as
diff --git a/nixos/doc/manual/release-notes/rl-2405.section.md b/nixos/doc/manual/release-notes/rl-2405.section.md
index 0328865ba733d..3bb993ec33c6f 100644
--- a/nixos/doc/manual/release-notes/rl-2405.section.md
+++ b/nixos/doc/manual/release-notes/rl-2405.section.md
@@ -365,7 +365,7 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m
   This means that configuration now has to be done using [environment variables](https://hexdocs.pm/livebook/readme.html#environment-variables) instead of command line arguments.
   This has the further consequence that the `livebook` service configuration has changed.
 
-- `lua` interpreters default LUA_PATH and LUA_CPATH are not overriden by nixpkgs
+- `lua` interpreters default LUA_PATH and LUA_CPATH are not overridden by nixpkgs
   anymore, we patch LUA_ROOT instead which is more respectful to upstream.
 
 - `luarocks-packages-updater`'s .csv format, used to define lua packages to be updated, has changed: `src` (URL of a git repository) has now become `rockspec` (URL of a rockspec) to remove ambiguity regarding which rockspec to use and simplify implementation.
@@ -695,9 +695,7 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
   The previous native backends remain available but are now minimally maintained. Refer to [upstream documentation](https://doc.qt.io/qt-6/qtmultimedia-index.html#ffmpeg-as-the-default-backend) for further details about each platform.
 
 - `services.btrbk` now automatically selects and provides required compression
-  program depending on the configured `stream_compress` option. Since this
-  replaces the need for the `extraPackages` option, this option will be
-  deprecated in future releases.
+  program depending on the configured `stream_compress` option.
 
 - `services.github-runner` module has been removed. To configure a single GitHub Actions Runner refer to `services.github-runners.*`. Note that this will trigger a new runner registration.
 
@@ -732,7 +730,7 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
 - `services.postgresql.extraPlugins`' type has expanded. Previously it was a list of packages, now it can also be a function that returns such a list.
   For example a config line like ``services.postgresql.extraPlugins = with pkgs.postgresql_11.pkgs; [ postgis ];`` is recommended to be changed to ``services.postgresql.extraPlugins = ps: with ps; [ postgis ];``;
 
-- `services.slskd` has been refactored to include more configuation options in
+- `services.slskd` has been refactored to include more configuration options in
   the free-form `services.slskd.settings` option, and some defaults (including listen ports)
   have been changed to match the upstream defaults. Additionally, disk logging is now
   disabled by default, and the log rotation timer has been removed.
@@ -760,7 +758,7 @@ Use `services.pipewire.extraConfig` or `services.pipewire.configPackages` for Pi
 
 - `systemd` units can now specify the `Upholds=` and `UpheldBy=` unit dependencies via the aptly
   named `upholds` and `upheldBy` options. These options get systemd to enforce that the
-  dependencies remain continuosly running for as long as the dependent unit is in a running state.
+  dependencies remain continuously running for as long as the dependent unit is in a running state.
 
 - A stdenv's default set of hardening flags can now be set via its `bintools-wrapper`'s `defaultHardeningFlags` argument. A convenient stdenv adapter, `withDefaultHardeningFlags`, can be used to override an existing stdenv's `defaultHardeningFlags`.
 
diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md
index f8730cfc29d6d..f190cad8a25b8 100644
--- a/nixos/doc/manual/release-notes/rl-2411.section.md
+++ b/nixos/doc/manual/release-notes/rl-2411.section.md
@@ -8,9 +8,29 @@
 
 - [AMDVLK](https://github.com/GPUOpen-Drivers/AMDVLK), AMD's open source Vulkan driver, is now available to be configured as `hardware.amdgpu.amdvlk` option.
   This also allows configuring runtime settings of AMDVLK and enabling experimental features.
+- The `moonlight-qt` package ([Moonlight game streaming](https://moonlight-stream.org/)) now has HDR support on Linux systems.
+
+- `authelia` has been upgraded to version 4.38. This version brings several features and improvements which are detailed in the [release blog post](https://www.authelia.com/blog/4.38-release-notes/).
+  This release also deprecates some configuration keys, which are likely to be removed in future version 5.0, but they are still supported and expected to be working in the current version.
+
+- `compressDrv` can compress selected files in a derivation. `compressDrvWeb` compresses files for common web server usage (`.gz` with `zopfli`, `.br` with `brotli`).
+
+- `hardware.display` is a new module implementing workarounds for misbehaving monitors
+  through setting up custom EDID files and forcing kernel/framebuffer modes.
+
+- NixOS now has support for *automatic boot assessment* (see [here](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/)) for detailed description of the feature) for `systemd-boot` users. Available as [boot.loader.systemd-boot.bootCounting](#opt-boot.loader.systemd-boot.bootCounting.enable).
+
+- A new display-manager `services.displayManager.ly` was added.
+  It is a tui based replacement of sddm and lightdm for window manager users.
+  Users can use it by `services.displayManager.ly.enable` and config it by
+  `services.displayManager.ly.settings` to generate `/etc/ly/config.ini`
 
 ## New Services {#sec-release-24.11-new-services}
 
+- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr), proxy server to bypass Cloudflare protection. Available as [services.flaresolverr](#opt-services.flaresolverr.enable) service.
+
+- [Goatcounter](https://www.goatcounter.com/), Easy web analytics. No tracking of personal data. Available as [services.goatcounter](options.html#opt-services.goatcocunter.enable).
+
 - [Open-WebUI](https://github.com/open-webui/open-webui), a user-friendly WebUI
   for LLMs. Available as [services.open-webui](#opt-services.open-webui.enable)
   service.
@@ -19,12 +39,50 @@
 
 - [Flood](https://flood.js.org/), a beautiful WebUI for various torrent clients. Available as [services.flood](options.html#opt-services.flood).
 
+- [QGroundControl], a ground station support and configuration manager for the PX4 and APM Flight Stacks. Available as [programs.qgroundcontrol](options.html#opt-programs.qgroundcontrol.enable).
+
+- [Eintopf](https://eintopf.info), community event and calendar web application. Available as [services.eintopf](options.html#opt-services.eintopf).
+
+- [Radicle](https://radicle.xyz), an open source, peer-to-peer code collaboration stack built on Git. Available as [services.radicle](#opt-services.radicle.enable).
+
+- [ddns-updater](https://github.com/qdm12/ddns-updater), a service to update DNS records periodically with WebUI for many DNS providers. Available as [services.ddns-updater](#opt-services.ddns-updater.enable).
+
+- [Immersed VR](https://immersed.com/), a closed-source coworking platform. Available as [programs.immersed-vr](#opt-programs.immersed-vr.enable).
+
 - [Renovate](https://github.com/renovatebot/renovate), a dependency updating tool for various git forges and language ecosystems. Available as [services.renovate](#opt-services.renovate.enable).
 
+- [Music Assistant](https://music-assistant.io/), a music library manager for your offline and online music sources which can easily stream your favourite music to a wide range of supported players. Available as [services.music-assistant](#opt-services.music-assistant.enable).
+
+- [zeronsd](https://github.com/zerotier/zeronsd), a DNS server for ZeroTier users. Available with [services.zeronsd.servedNetworks](#opt-services.zeronsd.servedNetworks).
+
 - [wg-access-server](https://github.com/freifunkMUC/wg-access-server/), an all-in-one WireGuard VPN solution with a web ui for connecting devices. Available at [services.wg-access-server](#opt-services.wg-access-server.enable).
 
+- [Envision](https://gitlab.com/gabmus/envision), a UI for building, configuring and running Monado, the open source OpenXR runtime. Available as [programs.envision](#opt-programs.envision.enable).
+
+- [Localsend](https://localsend.org/), an open source cross-platform alternative to AirDrop. Available as [programs.localsend](#opt-programs.localsend.enable).
+
+- [cryptpad](https://cryptpad.org/), a privacy-oriented collaborative platform (docs/drive/etc), has been added back. Available as [services.cryptpad](#opt-services.cryptpad.enable).
+
+- [realm](https://github.com/zhboner/realm), a simple, high performance relay server written in rust. Available as [services.realm.enable](#opt-services.realm.enable).
+
+- [Gotenberg](https://gotenberg.dev), an API server for converting files to PDFs that can be used alongside Paperless-ngx. Available as [services.gotenberg](options.html#opt-services.gotenberg).
+
 - [Playerctld](https://github.com/altdesktop/playerctl), a daemon to track media player activity. Available as [services.playerctld](option.html#opt-services.playerctld).
 
+- [Glance](https://github.com/glanceapp/glance), a self-hosted dashboard that puts all your feeds in one place. Available as [services.glance](option.html#opt-services.glance).
+
+- [Apache Tika](https://github.com/apache/tika), a toolkit that detects and extracts metadata and text from over a thousand different file types. Available as [services.tika](option.html#opt-services.tika).
+
+- [Misskey](https://misskey-hub.net/en/), an interplanetary microblogging platform. Available as [services.misskey](options.html#opt-services.misskey).
+
+- [Improved File Manager](https://github.com/misterunknown/ifm), or IFM, a single-file web-based file manager.
+
+- [OpenGFW](https://github.com/apernet/OpenGFW), an implementation of the Great Firewall on Linux. Available as [services.opengfw](#opt-services.opengfw.enable).
+
+- [Rathole](https://github.com/rapiz1/rathole), a lightweight and high-performance reverse proxy for NAT traversal. Available as [services.rathole](#opt-services.rathole.enable).
+
+- [Proton Mail bridge](https://proton.me/mail/bridge), a desktop application that runs in the background, encrypting and decrypting messages as they enter and leave your computer. It lets you add your Proton Mail account to your favorite email client via IMAP/SMTP by creating a local email server on your computer.
+
 ## Backward Incompatibilities {#sec-release-24.11-incompatibilities}
 
 - `transmission` package has been aliased with a `trace` warning to `transmission_3`. Since [Transmission 4 has been released last year](https://github.com/transmission/transmission/releases/tag/4.0.0), and Transmission 3 will eventually go away, it was decided perform this warning alias to make people aware of the new version. The `services.transmission.package` defaults to `transmission_3` as well because the upgrade can cause data loss in certain specific usage patterns (examples: [#5153](https://github.com/transmission/transmission/issues/5153), [#6796](https://github.com/transmission/transmission/issues/6796)). Please make sure to back up to your data directory per your usage:
@@ -53,6 +111,11 @@
 
 - `nginx` package no longer includes `gd` and `geoip` dependencies. For enabling it, override `nginx` package with the optionals `withImageFilter` and `withGeoIP`.
 
+- `systemd.enableUnifiedCgroupHierarchy` option has been removed.
+  In systemd 256 support for cgroup v1 ('legacy' and 'hybrid' hierarchies) is now considered obsolete and systemd by default will refuse to boot under it.
+  To forcibly reenable cgroup v1 support, you can `set boot.kernelParams = [ "systemd.unified_cgroup_hierachy=0" "SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1" ]`.
+  NixOS does not officially support this configuration and might cause your system to be unbootable in future versions. You are on your own.
+
 - `openssh` and `openssh_hpn` are now compiled without Kerberos 5 / GSSAPI support in an effort to reduce the attack surface of the components for the majority of users. Users needing this support can
   use the new `opensshWithKerberos` and `openssh_hpnWithKerberos` flavors (e.g. `programs.ssh.package = pkgs.openssh_gssapi`).
 
@@ -60,16 +123,28 @@
   it is set, instead of the previous hardcoded default of
   `${networking.hostName}.${security.ipa.domain}`.
 
+- The `MSMTP_QUEUE` and `MSMTP_LOG` environment variables accepted by `msmtpq` have now been renamed to `MSMTPQ_Q` and `MSMTPQ_LOG` respectively.
+
 - The fcgiwrap module now allows multiple instances running as distinct users.
   The option `services.fgciwrap` now takes an attribute set of the
   configuration of each individual instance.
   This requires migrating any previous configuration keys from
-  `services.fcgiwrap.*` to `services.fcgiwrap.some-instance.*`.
+  `services.fcgiwrap.*` to `services.fcgiwrap.instances.some-instance.*`.
   The ownership and mode of the UNIX sockets created by this service are now
   configurable and private by default.
   Processes also now run as a dynamically allocated user by default instead of
   root.
 
+- `singularity-tools` have the `storeDir` argument removed from its override interface and use `builtins.storeDir` instead.
+
+- Two build helpers in `singularity-tools`, i.e., `mkLayer` and `shellScript`, are deprecated, as they are no longer involved in image-building. Maintainers will remove them in future releases.
+
+- The `budgie` and `budgiePlugins` scope have been removed and their packages
+  moved into the top level scope (i.e., `budgie.budgie-desktop` is now
+  `budgie-desktop`)
+
+- All Cinnamon and XApp packages have been moved to top-level (i.e., `cinnamon.nemo` is now `nemo`).
+
 - `services.cgit` now runs as the cgit user by default instead of root.
   This change requires granting access to the repositories to this user or
   setting the appropriate one through `services.cgit.some-instance.user`.
@@ -78,6 +153,24 @@
   nvimpager settings: user commands in `-c` and `--cmd` now override the
   respective default settings because they are executed later.
 
+- Kubernetes `featureGates` have changed from a `listOf str` to `attrsOf bool`.
+  This refactor makes it possible to also disable feature gates, without having
+  to use `extraOpts` flags.
+
+  A previous configuration may have looked like this:
+  ```nix
+  featureGates = [ "EphemeralContainers" ];
+  extraOpts = pkgs.lib.concatStringsSep " " (
+  [
+    ''--feature-gates="CSIMigration=false"''
+  });
+  ```
+
+  Using an AttrSet instead, the new configuration would be:
+  ```nix
+  featureGates = {EphemeralContainers = true; CSIMigration=false;};
+  ```
+
 - `pkgs.nextcloud27` has been removed since it's EOL.
 
 - `services.forgejo.mailerPasswordFile` has been deprecated by the drop-in replacement `services.forgejo.secrets.mailer.PASSWD`,
@@ -87,6 +180,12 @@
 
 - `services.ddclient.use` has been deprecated: `ddclient` now supports separate IPv4 and IPv6 configuration. Use `services.ddclient.usev4` and `services.ddclient.usev6` instead.
 
+- `teleport` has been upgraded from major version 15 to major version 16.
+  Refer to upstream [upgrade instructions](https://goteleport.com/docs/management/operations/upgrading/)
+  and [release notes for v16](https://goteleport.com/docs/changelog/#1600-061324).
+
+- `tests.overriding` has its `passthru.tests` restructured as an attribute set instead of a list, making individual tests accessible by their names.
+
 - `vaultwarden` lost the capability to bind to privileged ports. If you rely on
    this behavior, override the systemd unit to allow `CAP_NET_BIND_SERVICE` in
    your local configuration.
@@ -94,11 +193,17 @@
 - The Invoiceplane module now only accepts the structured `settings` option.
   `extraConfig` is now removed.
 
+- The `ollama` services replaces its `sandbox` toggle with options to configure
+  a static `user` and `group`. The `writablePaths` option has been removed and
+  the models directory is now always exempt from sandboxing.
+
 - Legacy package `stalwart-mail_0_6` was dropped, please note the
   [manual upgrade process](https://github.com/stalwartlabs/mail-server/blob/main/UPGRADING.md)
   before changing the package to `pkgs.stalwart-mail` in
   [`services.stalwart-mail.package`](#opt-services.stalwart-mail.package).
 
+- The `nomad_1_5` package was dropped, as [it has reached end-of-life upstream](https://support.hashicorp.com/hc/en-us/articles/360021185113-Support-Period-and-End-of-Life-EOL-Policy). Evaluating it will throw an error.
+
 - `androidndkPkgs` has been updated to `androidndkPkgs_26`.
 
 - Android NDK version 26 and SDK version 33 are now the default versions used for cross compilation to android.
@@ -107,6 +212,10 @@
   and `nodePackages.vscode-json-languageserver-bin` were dropped due to an unmaintained upstream.
   The `vscode-langservers-extracted` package is a maintained drop-in replacement.
 
+- `fetchNextcloudApp` has been rewritten to use `fetchurl` rather than
+  `fetchzip`. This invalidates all existing hashes but you can restore the old
+  behavior by passing it `unpack = true`.
+
 - `haskell.lib.compose.justStaticExecutables` now disallows references to GHC in the
   output by default, to alert users to closure size issues caused by
   [#164630](https://github.com/NixOS/nixpkgs/issues/164630). See ["Packaging
@@ -134,6 +243,8 @@
   services.shiori.environmentFile = "/path/to/env-file";
   ```
 
+- `/share/nano` is now only linked when `programs.nano.enable` is enabled.
+
 - `libe57format` has been updated to `>= 3.0.0`, which contains some backward-incompatible API changes. See the [release note](https://github.com/asmaloney/libE57Format/releases/tag/v3.0.0) for more details.
 
 - `gitlab` deprecated support for *runner registration tokens* in GitLab 16.0, disabled their support in GitLab 17.0 and will
@@ -146,6 +257,12 @@
 
 - `gitlab` has been updated from 16.x to 17.x and requires at least `postgresql` 14.9, as stated in the [documentation](https://docs.gitlab.com/17.1/ee/install/requirements.html#postgresql-requirements). Check the [upgrade guide](#module-services-postgres-upgrading) in the NixOS manual on how to upgrade your PostgreSQL installation.
 
+- `gitaly` (part of `gitlab`) is now using the bundled `git` package instead of `pkgs.git` to maintain compatibility with GitLab.
+
+- `nixos/gitlab` no longer adds `pkgs.git` to `environment.systemPackages` by default.
+
+- The `replay-sorcery` package and module was removed as it unmaintained upstream. Consider using `gpu-screen-recorder` or `obs-studio` instead.
+
 - `zx` was updated to v8, which introduces several breaking changes.
   See the [v8 changelog](https://github.com/google/zx/releases/tag/8.0.0) for more information.
 
@@ -162,9 +279,15 @@
   Explicitly set `kubelet.hostname` to `networking.fqdnOrHostName` to get back
   the old default behavior.
 
+- Docker now defaults to 27.x, because version 24.x stopped receiving security updates and bug fixes after [February 1, 2024](https://github.com/moby/moby/pull/46772#discussion_r1686464084).
+
 - `keycloak` was updated to version 25, which introduces new hostname related options.
   See [Upgrading Guide](https://www.keycloak.org/docs/25.0.1/upgrading/#migrating-to-25-0-0) for instructions.
 
+- `programs.vim.defaultEditor` now only works if `programs.vim.enable` is enabled.
+
+- `/share/vim-plugins` now only gets linked if `programs.vim.enable` is enabled
+
 - The `tracy` package no longer works on X11, since it's moved to Wayland
   support, which is the intended default behavior by Tracy maintainers.
   X11 users have to switch to the new package `tracy-x11`.
@@ -172,27 +295,119 @@
 - The `services.prometheus.exporters.minio` option has been removed, as it's upstream implementation was broken and unmaintained.
   Minio now has built-in [Prometheus metrics exposure](https://min.io/docs/minio/linux/operations/monitoring/collect-minio-metrics-using-prometheus.html), which can be used instead.
 
+- The `services.patroni.raft` option has been removed, as Raft has been [deprecated by upstream since 3.0.0](https://github.com/patroni/patroni/blob/master/docs/releases.rst#version-300)
+
+- `services.roundcube.maxAttachmentSize` will multiply the value set with `1.37` to offset overhead introduced by the base64 encoding applied to attachments.
+
+- The `sound` options have been removed or renamed, as they had a lot of unintended side effects. See [below](#sec-release-24.11-migration-sound) for details.
+
+- The `services.mxisd` module has been removed as both [mxisd](https://github.com/kamax-matrix/mxisd) and [ma1sd](https://github.com/ma1uta/ma1sd) are not maintained any longer.
+  Consequently the package `pkgs.ma1sd` has also been removed.
+
+- `ffmpeg_5` has been removed. Please use the unversioned `ffmpeg`,
+  pin a newer version, or if necessary pin `ffmpeg_4` for compatibility.
+
+- The `xdg.portal.gtkUsePortal` option has been removed, as it had been deprecated for over 2 years. Using the `GTK_USE_PORTAL` environment variable in this manner is not intended nor encouraged by the GTK developers, but can still be done manually via `environment.sessionVariables`.
+
+- The `services.trust-dns` module has been renamed to `services.hickory-dns`.
+
 ## Other Notable Changes {#sec-release-24.11-notable-changes}
 
 <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
 
+- The `zerocallusedregs` hardening flag is enabled by default on compilers that support it.
+
+- The `stackclashprotection` hardening flag has been added, though disabled by default.
+
+- `cargoSha256` in `rustPlatform.buildRustPackage` has been deprecated in favor
+  of `cargoHash` which supports SRI hashes. See
+  [buildRustPackage: Compiling Rust applications with Cargo](https://nixos.org/manual/nixpkgs/unstable/#compiling-rust-applications-with-cargo)
+  for more information.
+
 - `hareHook` has been added as the language framework for Hare. From now on, it,
   not the `hare` package, should be added to `nativeBuildInputs` when building
   Hare programs.
 
 - [`lib.options.mkPackageOptionMD`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.options.mkPackageOptionMD) is now obsolete; use the identical [`lib.options.mkPackageOption`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.options.mkPackageOption) instead.
 
+- `lib.misc.mapAttrsFlatten` is now formally deprecated and will be removed in future releases; use the identical [`lib.attrsets.mapAttrsToList`](https://nixos.org/manual/nixpkgs/unstable#function-library-lib.attrsets.mapAttrsToList) instead.
+
+- `nixosTests` now provide a working IPv6 setup for VLAN 1 by default.
+
 - To facilitate dependency injection, the `imgui` package now builds a static archive using vcpkg' CMake rules.
   The derivation now installs "impl" headers selectively instead of by a wildcard.
   Use `imgui.src` if you just want to access the unpacked sources.
 
+- The `i18n.inputMethod` module introduces two new properties:
+  `enable` and `type`, for declaring whether to enable an alternative input method and defining which input method respectfully. The options available in `type` are the same as the existing `enabled` option. `enabled` is now deprecated, and will be removed in a future release.
+
+- `security.pam.u2f` now follows RFC42.
+  All module options are now settable through the freeform `.settings`.
+
+- Gollum was upgraded to major version 6. Read their [migration notes](https://github.com/gollum/gollum/wiki/6.0-Release-Notes).
+
+- The hooks `yarnConfigHook` and `yarnBuildHook` were added. These should replace `yarn2nix.mkYarnPackage` and other `yarn2nix` related tools. The motivation to get rid of `yarn2nix` tools is the fact that they are too complex and hard to maintain, and they rely upon too much Nix evaluation which is problematic if import-from-derivation is not allowed (see more details at [#296856](https://github.com/NixOS/nixpkgs/issues/296856). The transition from `mkYarnPackage` to `yarn{Config,Build}Hook` is tracked at [#324246](https://github.com/NixOS/nixpkgs/issues/324246).
+
 - Cinnamon has been updated to 6.2.
   - Following Mint 22 defaults, the Cinnamon module no longer ships geary and hexchat by default.
   - Nemo is now built with gtk-layer-shell support, note that for now it will be expected to see nemo-desktop
     listed as a regular entry in Cinnamon Wayland session's window list applet.
 
+- `restic` module now has an option for inhibiting system sleep while backups are running, defaulting to off (not inhibiting sleep), available as [`services.restic.backups.<name>.inhibitsSleep`](#opt-services.restic.backups._name_.inhibitsSleep).
+
 - Support for *runner registration tokens* has been [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872)
   in `gitlab-runner` 15.6 and is expected to be removed in `gitlab-runner` 18.0. Configuration of existing runners
   should be changed to using *runner authentication tokens* by configuring
   {option}`services.gitlab-runner.services.<name>.authenticationTokenConfigFile` instead of the former
   {option}`services.gitlab-runner.services.<name>.registrationConfigFile` option.
+
+- `iproute2` now has libbpf support.
+
+- `nix.channel.enable = false` no longer implies `nix.settings.nix-path = []`.
+  Since Nix 2.13, a `nix-path` set in `nix.conf` cannot be overriden by the `NIX_PATH` configuration variable.
+
+- Buildkite Agents are now each running in their own private `/tmp`.
+  To return to the old behaviour, set `systemd.services.buildkite-agent-${name}.serviceConfig.PrivateTmp = false;`.
+
+## Detailed migration information {#sec-release-24.11-migration}
+
+### `sound` options removal {#sec-release-24.11-migration-sound}
+
+The `sound` options have been largely removed, as they are unnecessary for most modern setups, and cause issues when enabled.
+
+If you set `sound.enable` in your configuration:
+  - If you are using Pulseaudio or PipeWire, simply remove that option
+  - If you are not using an external sound server, and want volumes to be persisted across shutdowns, set `hardware.alsa.enablePersistence = true` instead
+
+If you set `sound.enableOSSEmulation` in your configuration:
+  - Make sure it is still necessary, as very few applications actually use OSS
+  - If necessary, set `boot.kernelModules = [ "snd_pcm_oss" ]`
+
+If you set `sound.extraConfig` in your configuration:
+  - If you are using another sound server, like Pulseaudio, JACK or PipeWire, migrate your configuration to that
+  - If you are not using an external sound server, set `environment.etc."asound.conf".text = yourExtraConfig` instead
+
+If you set `sound.mediaKeys` in your configuration:
+  - Preferably switch to handling media keys in your desktop environment/compositor
+  - If you want to maintain the exact behavior of the option, use the following snippet
+
+```nix
+services.actkbd = let
+  volumeStep = "1%";
+in {
+  enable = true;
+  bindings = [
+    # "Mute" media key
+    { keys = [ 113 ]; events = [ "key" ];       command = "${alsa-utils}/bin/amixer -q set Master toggle"; }
+
+    # "Lower Volume" media key
+    { keys = [ 114 ]; events = [ "key" "rep" ]; command = "${alsa-utils}/bin/amixer -q set Master ${volumeStep}- unmute"; }
+
+    # "Raise Volume" media key
+    { keys = [ 115 ]; events = [ "key" "rep" ]; command = "${alsa-utils}/bin/amixer -q set Master ${volumeStep}+ unmute"; }
+
+    # "Mic Mute" media key
+    { keys = [ 190 ]; events = [ "key" ];       command = "${alsa-utils}/bin/amixer -q set Capture toggle"; }
+  ];
+};
+```
diff --git a/nixos/doc/manual/shell.nix b/nixos/doc/manual/shell.nix
index 70500a12b0374..7765358ddb328 100644
--- a/nixos/doc/manual/shell.nix
+++ b/nixos/doc/manual/shell.nix
@@ -1,20 +1,13 @@
 let
   pkgs = import ../../.. {
-    config = {};
-    overlays = [];
+    config = { };
+    overlays = [ ];
   };
 
   common = import ./common.nix;
   inherit (common) outputPath indexPath;
-
-  web-devmode = import ../../../pkgs/tools/nix/web-devmode.nix {
-    inherit pkgs;
-    buildArgs = "../../release.nix -A manualHTML.${builtins.currentSystem}";
-    open = "/${outputPath}/${indexPath}";
-  };
 in
-  pkgs.mkShell {
-    packages = [
-      web-devmode
-    ];
-  }
+pkgs.callPackage ../../../pkgs/tools/nix/web-devmode.nix {
+  buildArgs = "../../release.nix -A manualHTML.${builtins.currentSystem}";
+  open = "/${outputPath}/${indexPath}";
+}
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index 1220bbfd5ed7c..208eaf59dc5c0 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -202,13 +202,11 @@ assert (lib.assertMsg (lib.all
               == ((attrs.group or null) == null))
         contents) "Contents of the disk image should set none of {user, group} or both at the same time.");
 
-with lib;
-
 let format' = format; in let
 
   format = if format' == "qcow2-compressed" then "qcow2" else format';
 
-  compress = optionalString (format' == "qcow2-compressed") "-c";
+  compress = lib.optionalString (format' == "qcow2-compressed") "-c";
 
   filename = "nixos." + {
     qcow2 = "qcow2";
@@ -240,7 +238,7 @@ let format' = format; in let
         mkpart primary ext4 2MB -1 \
         align-check optimal 2 \
         print
-      ${optionalString deterministic ''
+      ${lib.optionalString deterministic ''
           sgdisk \
           --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
           --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
@@ -255,7 +253,7 @@ let format' = format; in let
         mkpart ESP fat32 8MiB ${bootSize} \
         set 1 boot on \
         mkpart primary ext4 ${bootSize} -1
-      ${optionalString deterministic ''
+      ${lib.optionalString deterministic ''
           sgdisk \
           --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
           --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
@@ -271,7 +269,7 @@ let format' = format; in let
         mkpart BOOT fat32 100MiB ${bootSize} \
         set 2 bls_boot on \
         mkpart ROOT ext4 ${bootSize} -1
-      ${optionalString deterministic ''
+      ${lib.optionalString deterministic ''
           sgdisk \
           --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
           --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC  \
@@ -288,7 +286,7 @@ let format' = format; in let
         mkpart no-fs 0 1024KiB \
         set 2 bios_grub on \
         mkpart primary ext4 ${bootSize} -1
-      ${optionalString deterministic ''
+      ${lib.optionalString deterministic ''
           sgdisk \
           --disk-guid=97FD5997-D90B-4AA3-8D16-C1723AEA73C \
           --partition-guid=1:1C06F03B-704E-4657-B9CD-681A087A2FDC \
@@ -302,7 +300,7 @@ let format' = format; in let
 
   useEFIBoot = touchEFIVars;
 
-  nixpkgs = cleanSource pkgs.path;
+  nixpkgs = lib.cleanSource pkgs.path;
 
   # FIXME: merge with channel.nix / make-channel.nix.
   channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}" {} ''
@@ -316,8 +314,8 @@ let format' = format; in let
     echo -n ${config.system.nixos.versionSuffix} > $out/nixos/.version-suffix
   '';
 
-  binPath = with pkgs; makeBinPath (
-    [ rsync
+  binPath = lib.makeBinPath (with pkgs; [
+      rsync
       util-linux
       parted
       e2fsprogs
@@ -342,7 +340,7 @@ let format' = format; in let
   basePaths = [ config.system.build.toplevel ]
     ++ lib.optional copyChannel channelSources;
 
-  additionalPaths' = subtractLists basePaths additionalPaths;
+  additionalPaths' = lib.subtractLists basePaths additionalPaths;
 
   closureInfo = pkgs.closureInfo {
     rootPaths = basePaths ++ additionalPaths';
@@ -389,9 +387,9 @@ let format' = format; in let
     # Semi-shamelessly copied from make-etc.sh. I (@copumpkin) shall factor this stuff out as part of
     # https://github.com/NixOS/nixpkgs/issues/23052.
     set -f
-    sources_=(${concatStringsSep " " sources})
-    targets_=(${concatStringsSep " " targets})
-    modes_=(${concatStringsSep " " modes})
+    sources_=(${lib.concatStringsSep " " sources})
+    targets_=(${lib.concatStringsSep " " targets})
+    modes_=(${lib.concatStringsSep " " modes})
     set +f
 
     for ((i = 0; i < ''${#targets_[@]}; i++)); do
@@ -443,8 +441,8 @@ let format' = format; in let
       ${if copyChannel then "--channel ${channelSources}" else "--no-channel-copy"} \
       --substituters ""
 
-    ${optionalString (additionalPaths' != []) ''
-      nix --extra-experimental-features nix-command copy --to $root --no-check-sigs ${concatStringsSep " " additionalPaths'}
+    ${lib.optionalString (additionalPaths' != []) ''
+      nix --extra-experimental-features nix-command copy --to $root --no-check-sigs ${lib.concatStringsSep " " additionalPaths'}
     ''}
 
     diskImage=nixos.raw
@@ -514,10 +512,10 @@ let format' = format; in let
     ''}
 
     echo "copying staging root to image..."
-    cptofs -p ${optionalString (partitionTableType != "none") "-P ${rootPartition}"} \
+    cptofs -p ${lib.optionalString (partitionTableType != "none") "-P ${rootPartition}"} \
            -t ${fsType} \
            -i $diskImage \
-           $root${optionalString onlyNixStore builtins.storeDir}/* / ||
+           $root${lib.optionalString onlyNixStore builtins.storeDir}/* / ||
       (echo >&2 "ERROR: cptofs failed. diskSize might be too small for closure."; exit 1)
   '';
 
@@ -547,7 +545,7 @@ let format' = format; in let
       buildInputs = with pkgs; [ util-linux e2fsprogs dosfstools ];
       postVM = moveOrConvertImage + createHydraBuildProducts + postVM;
       QEMU_OPTS =
-        concatStringsSep " " (lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
+        lib.concatStringsSep " " (lib.optional useEFIBoot "-drive if=pflash,format=raw,unit=0,readonly=on,file=${efiFirmware}"
         ++ lib.optionals touchEFIVars [
           "-drive if=pflash,format=raw,unit=1,file=$efiVars"
         ] ++ lib.optionals (OVMF.systemManagementModeRequired or false) [
@@ -564,8 +562,8 @@ let format' = format; in let
       # It is necessary to set root filesystem unique identifier in advance, otherwise
       # bootloader might get the wrong one and fail to boot.
       # At the end, we reset again because we want deterministic timestamps.
-      ${optionalString (fsType == "ext4" && deterministic) ''
-        tune2fs -T now ${optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
+      ${lib.optionalString (fsType == "ext4" && deterministic) ''
+        tune2fs -T now ${lib.optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
       ''}
       # make systemd-boot find ESP without udev
       mkdir /dev/block
@@ -577,33 +575,33 @@ let format' = format; in let
 
       # Create the ESP and mount it. Unlike e2fsprogs, mkfs.vfat doesn't support an
       # '-E offset=X' option, so we can't do this outside the VM.
-      ${optionalString (partitionTableType == "efi" || partitionTableType == "hybrid") ''
+      ${lib.optionalString (partitionTableType == "efi" || partitionTableType == "hybrid") ''
         mkdir -p /mnt/boot
         mkfs.vfat -n ESP /dev/vda1
         mount /dev/vda1 /mnt/boot
 
-        ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
+        ${lib.optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
       ''}
-      ${optionalString (partitionTableType == "efixbootldr") ''
+      ${lib.optionalString (partitionTableType == "efixbootldr") ''
         mkdir -p /mnt/{boot,efi}
         mkfs.vfat -n ESP /dev/vda1
         mkfs.vfat -n BOOT /dev/vda2
         mount /dev/vda1 /mnt/efi
         mount /dev/vda2 /mnt/boot
 
-        ${optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
+        ${lib.optionalString touchEFIVars "mount -t efivarfs efivarfs /sys/firmware/efi/efivars"}
       ''}
 
       # Install a configuration.nix
       mkdir -p /mnt/etc/nixos
-      ${optionalString (configFile != null) ''
+      ${lib.optionalString (configFile != null) ''
         cp ${configFile} /mnt/etc/nixos/configuration.nix
       ''}
 
       ${lib.optionalString installBootLoader ''
         # In this throwaway resource, we only have /dev/vda, but the actual VM may refer to another disk for bootloader, e.g. /dev/vdb
         # Use this option to create a symlink from vda to any arbitrary device you want.
-        ${optionalString (config.boot.loader.grub.enable) (lib.concatMapStringsSep " " (device:
+        ${lib.optionalString (config.boot.loader.grub.enable) (lib.concatMapStringsSep " " (device:
           lib.optionalString (device != "/dev/vda") ''
             mkdir -p "$(dirname ${device})"
             ln -s /dev/vda ${device}
@@ -625,9 +623,9 @@ let format' = format; in let
 
       # Set the ownerships of the contents. The modes are set in preVM.
       # No globbing on targets, so no need to set -f
-      targets_=(${concatStringsSep " " targets})
-      users_=(${concatStringsSep " " users})
-      groups_=(${concatStringsSep " " groups})
+      targets_=(${lib.concatStringsSep " " targets})
+      users_=(${lib.concatStringsSep " " users})
+      groups_=(${lib.concatStringsSep " " groups})
       for ((i = 0; i < ''${#targets_[@]}; i++)); do
         target="''${targets_[$i]}"
         user="''${users_[$i]}"
@@ -646,9 +644,9 @@ let format' = format; in let
       # In deterministic mode, this is fixed to 1970-01-01 (UNIX timestamp 0).
       # This two-step approach is necessary otherwise `tune2fs` will want a fresher filesystem to perform
       # some changes.
-      ${optionalString (fsType == "ext4") ''
-        tune2fs -T now ${optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
-        ${optionalString deterministic "tune2fs -f -T 19700101 $rootDisk"}
+      ${lib.optionalString (fsType == "ext4") ''
+        tune2fs -T now ${lib.optionalString deterministic "-U ${rootFSUID}"} -c 0 -i 0 $rootDisk
+        ${lib.optionalString deterministic "tune2fs -f -T 19700101 $rootDisk"}
       ''}
     ''
   );
diff --git a/nixos/lib/make-options-doc/default.nix b/nixos/lib/make-options-doc/default.nix
index 17e03baf3bb75..210fbd1a0e997 100644
--- a/nixos/lib/make-options-doc/default.nix
+++ b/nixos/lib/make-options-doc/default.nix
@@ -1,5 +1,5 @@
 /**
-  Generates documentation for [nix modules](https://nix.dev/tutorials/module-system/module-system.html).
+  Generates documentation for [nix modules](https://nix.dev/tutorials/module-system/index.html).
 
   It uses the declared `options` to generate documentation in various formats.
 
diff --git a/nixos/lib/systemd-lib.nix b/nixos/lib/systemd-lib.nix
index dac5cc7b700c8..fedd85f09b805 100644
--- a/nixos/lib/systemd-lib.nix
+++ b/nixos/lib/systemd-lib.nix
@@ -12,6 +12,7 @@ let
     concatStringsSep
     const
     elem
+    elemAt
     filter
     filterAttrs
     flatten
@@ -21,11 +22,14 @@ let
     isFloat
     isList
     isPath
+    isString
     length
     makeBinPath
     makeSearchPathOutput
     mapAttrs
     mapAttrsToList
+    mapNullable
+    match
     mkAfter
     mkIf
     optional
@@ -101,6 +105,8 @@ in rec {
     optional (attr ? ${name} && ! isByteFormat attr.${name})
       "Systemd ${group} field `${name}' must be in byte format [0-9]+[KMGT].";
 
+  toIntBaseDetected = value: assert (match "[0-9]+|0x[0-9a-fA-F]+" value) != null; (builtins.fromTOML "v=${value}").v;
+
   hexChars = stringToCharacters "0123456789abcdefABCDEF";
 
   isMacAddress = s: stringLength s == 17
@@ -156,6 +162,23 @@ in rec {
     optional (attr ? ${name} && !(((isInt attr.${name} || isFloat attr.${name}) && min <= attr.${name} && max >= attr.${name}) || elem attr.${name} values))
       "Systemd ${group} field `${name}' is not a value in range [${toString min},${toString max}], or one of ${toString values}";
 
+  assertRangeWithOptionalMask = name: min: max: group: attr:
+    if (attr ? ${name}) then
+      if isInt attr.${name} then
+        assertRange name min max group attr
+      else if isString attr.${name} then
+        let
+          fields = match "([0-9]+|0x[0-9a-fA-F]+)(/([0-9]+|0x[0-9a-fA-F]+))?" attr.${name};
+        in if fields == null then ["Systemd ${group} field `${name}' must either be an integer or two integers separated by a slash (/)."]
+        else let
+          value = toIntBaseDetected (elemAt fields 0);
+          mask = mapNullable toIntBaseDetected (elemAt fields 2);
+        in
+          optional (!(min <= value && max >= value)) "Systemd ${group} field `${name}' has main value outside the range [${toString min},${toString max}]."
+          ++ optional (mask != null && !(min <= mask && max >= mask)) "Systemd ${group} field `${name}' has mask outside the range [${toString min},${toString max}]."
+      else ["Systemd ${group} field `${name}' must either be an integer or a string."]
+    else [];
+
   assertMinimum = name: min: group: attr:
     optional (attr ? ${name} && attr.${name} < min)
       "Systemd ${group} field `${name}' must be greater than or equal to ${toString min}";
@@ -169,6 +192,10 @@ in rec {
     optional (attr ? ${name} && !isInt attr.${name})
       "Systemd ${group} field `${name}' is not an integer";
 
+  assertRemoved = name: see: group: attr:
+    optional (attr ? ${name})
+      "Systemd ${group} field `${name}' has been removed. See ${see}";
+
   checkUnitConfig = group: checks: attrs: let
     # We're applied at the top-level type (attrsOf unitOption), so the actual
     # unit options might contain attributes from mkOverride and mkIf that we need to
diff --git a/nixos/lib/systemd-types.nix b/nixos/lib/systemd-types.nix
index f3bc8e06d9cb9..65ddb2458627c 100644
--- a/nixos/lib/systemd-types.nix
+++ b/nixos/lib/systemd-types.nix
@@ -45,12 +45,61 @@ let
 
   inherit (lib.types)
     attrsOf
+    coercedTo
+    enum
     lines
     listOf
     nullOr
+    oneOf
+    package
     path
+    singleLineStr
     submodule
     ;
+
+  initrdStorePathModule = { config, ... }: {
+    options = {
+      enable = (mkEnableOption "copying of this file and symlinking it") // { default = true; };
+
+      target = mkOption {
+        type = nullOr path;
+        description = ''
+          Path of the symlink.
+        '';
+        default = null;
+      };
+
+      source = mkOption {
+        type = path;
+        description = "Path of the source file.";
+      };
+
+      dlopen = {
+        usePriority = mkOption {
+          type = enum [ "required" "recommended" "suggested" ];
+          default = "recommended";
+          description = ''
+            Priority of dlopen ELF notes to include. "required" is
+            minimal, "recommended" includes "required", and
+            "suggested" includes "recommended".
+
+            See: https://systemd.io/ELF_DLOPEN_METADATA/
+          '';
+        };
+
+        features = mkOption {
+          type = listOf singleLineStr;
+          default = [ ];
+          description = ''
+            Features to enable via dlopen ELF notes. These will be in
+            addition to anything included via 'usePriority',
+            regardless of their priority.
+          '';
+        };
+      };
+    };
+  };
+
 in
 
 {
@@ -86,31 +135,23 @@ in
   automounts = listOf (submodule [ stage2AutomountOptions unitConfig automountConfig ]);
   initrdAutomounts = attrsOf (submodule [ stage1AutomountOptions unitConfig automountConfig ]);
 
+  initrdStorePath = listOf (coercedTo
+    (oneOf [ singleLineStr package ])
+    (source: { inherit source; })
+    (submodule initrdStorePathModule));
+
   initrdContents = attrsOf (submodule ({ config, options, name, ... }: {
+    imports = [ initrdStorePathModule ];
     options = {
-      enable = (mkEnableOption "copying of this file and symlinking it") // { default = true; };
-
-      target = mkOption {
-        type = path;
-        description = ''
-          Path of the symlink.
-        '';
-        default = name;
-      };
-
       text = mkOption {
         default = null;
         type = nullOr lines;
         description = "Text of the file.";
       };
-
-      source = mkOption {
-        type = path;
-        description = "Path of the source file.";
-      };
     };
 
     config = {
+      target = mkDefault name;
       source = mkIf (config.text != null) (
         let name' = "initrd-" + baseNameOf name;
         in mkDerivedConfig options.text (pkgs.writeText name')
diff --git a/nixos/lib/test-driver/shell.nix b/nixos/lib/test-driver/shell.nix
index 367bbad556c03..8879b1a0dc4c0 100644
--- a/nixos/lib/test-driver/shell.nix
+++ b/nixos/lib/test-driver/shell.nix
@@ -1,2 +1,4 @@
-with import ../../.. {};
-pkgs.callPackage ./default.nix {}
+{
+  pkgs ? import ../../.. { },
+}:
+pkgs.callPackage ./default.nix { }
diff --git a/nixos/lib/testing/network.nix b/nixos/lib/testing/network.nix
index 0f1615a0ad3b9..8e6d383e6257f 100644
--- a/nixos/lib/testing/network.nix
+++ b/nixos/lib/testing/network.nix
@@ -32,10 +32,19 @@ let
       # Automatically assign IP addresses to requested interfaces.
       assignIPs = lib.filter (i: i.assignIP) interfaces;
       ipInterfaces = forEach assignIPs (i:
-        nameValuePair i.name { ipv4.addresses =
-          [ { address = "192.168.${toString i.vlan}.${toString config.virtualisation.test.nodeNumber}";
+        nameValuePair i.name {
+          ipv4.addresses = [
+            {
+              address = "192.168.${toString i.vlan}.${toString config.virtualisation.test.nodeNumber}";
               prefixLength = 24;
-            }];
+            }
+          ];
+          ipv6.addresses = [
+            {
+              address = "2001:db8:${toString i.vlan}::${toString config.virtualisation.test.nodeNumber}";
+              prefixLength = 64;
+            }
+          ];
         });
 
       qemuOptions = lib.flatten (forEach interfacesNumbered ({ fst, snd }:
@@ -53,6 +62,9 @@ let
           networking.primaryIPAddress =
             optionalString (ipInterfaces != [ ]) (head (head ipInterfaces).value.ipv4.addresses).address;
 
+          networking.primaryIPv6Address =
+            optionalString (ipInterfaces != [ ]) (head (head ipInterfaces).value.ipv6.addresses).address;
+
           # Put the IP addresses of all VMs in this machine's
           # /etc/hosts file.  If a machine has multiple
           # interfaces, use the IP address corresponding to
@@ -60,12 +72,16 @@ let
           # virtualisation.vlans option).
           networking.extraHosts = flip concatMapStrings (attrNames nodes)
             (m':
-              let config = nodes.${m'}; in
+              let
+                config = nodes.${m'};
+                hostnames =
+                  optionalString (config.networking.domain != null) "${config.networking.hostName}.${config.networking.domain} " +
+                  "${config.networking.hostName}\n";
+              in
               optionalString (config.networking.primaryIPAddress != "")
-                ("${config.networking.primaryIPAddress} " +
-                  optionalString (config.networking.domain != null)
-                    "${config.networking.hostName}.${config.networking.domain} " +
-                  "${config.networking.hostName}\n"));
+                "${config.networking.primaryIPAddress} ${hostnames}" +
+              optionalString (config.networking.primaryIPv6Address != "")
+                ("${config.networking.primaryIPv6Address} ${hostnames}"));
 
           virtualisation.qemu.options = qemuOptions;
           boot.initrd.services.udev.rules = concatMapStrings (x: x + "\n") udevRules;
diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix
index c1c1828a2c12c..82bbfae0178b0 100644
--- a/nixos/lib/utils.nix
+++ b/nixos/lib/utils.nix
@@ -23,6 +23,7 @@ let
     isPath
     isString
     listToAttrs
+    mapAttrs
     nameValuePair
     optionalString
     removePrefix
@@ -140,11 +141,35 @@ utils = rec {
          ];
        } "_secret" -> { ".example[1].relevant.secret" = "/path/to/secret"; }
   */
-  recursiveGetAttrWithJqPrefix = item: attr:
+  recursiveGetAttrWithJqPrefix = item: attr: mapAttrs (_name: set: set.${attr}) (recursiveGetAttrsetWithJqPrefix item attr);
+
+  /* Similar to `recursiveGetAttrWithJqPrefix`, but returns the whole
+     attribute set containing `attr` instead of the value of `attr` in
+     the set.
+
+     Example:
+       recursiveGetAttrsetWithJqPrefix {
+         example = [
+           {
+             irrelevant = "not interesting";
+           }
+           {
+             ignored = "ignored attr";
+             relevant = {
+               secret = {
+                 _secret = "/path/to/secret";
+                 quote = true;
+               };
+             };
+           }
+         ];
+       } "_secret" -> { ".example[1].relevant.secret" = { _secret = "/path/to/secret"; quote = true; }; }
+  */
+  recursiveGetAttrsetWithJqPrefix = item: attr:
     let
       recurse = prefix: item:
         if item ? ${attr} then
-          nameValuePair prefix item.${attr}
+          nameValuePair prefix item
         else if isDerivation item then []
         else if isAttrs item then
           map (name:
@@ -206,6 +231,58 @@ utils = rec {
            }
          ]
        }
+
+     The attribute set { _secret = "/path/to/secret"; } can contain extra
+     options, currently it accepts the `quote = true|false` option.
+
+     If `quote = true` (default behavior), the content of the secret file will
+     be quoted as a string and embedded.  Otherwise, if `quote = false`, the
+     content of the secret file will be parsed to JSON and then embedded.
+
+     Example:
+       If the file "/path/to/secret" contains the JSON document:
+
+       [
+         { "a": "topsecretpassword1234" },
+         { "b": "topsecretpassword5678" }
+       ]
+
+       genJqSecretsReplacementSnippet {
+         example = [
+           {
+             irrelevant = "not interesting";
+           }
+           {
+             ignored = "ignored attr";
+             relevant = {
+               secret = {
+                 _secret = "/path/to/secret";
+                 quote = false;
+               };
+             };
+           }
+         ];
+       } "/path/to/output.json"
+
+       would generate a snippet that, when run, outputs the following
+       JSON file at "/path/to/output.json":
+
+       {
+         "example": [
+           {
+             "irrelevant": "not interesting"
+           },
+           {
+             "ignored": "ignored attr",
+             "relevant": {
+               "secret": [
+                 { "a": "topsecretpassword1234" },
+                 { "b": "topsecretpassword5678" }
+               ]
+             }
+           }
+         ]
+       }
   */
   genJqSecretsReplacementSnippet = genJqSecretsReplacementSnippet' "_secret";
 
@@ -213,7 +290,11 @@ utils = rec {
   # attr which identifies the secret to be changed.
   genJqSecretsReplacementSnippet' = attr: set: output:
     let
-      secrets = recursiveGetAttrWithJqPrefix set attr;
+      secretsRaw = recursiveGetAttrsetWithJqPrefix set attr;
+      # Set default option values
+      secrets = mapAttrs (_name: set: {
+        quote = true;
+      } // set) secretsRaw;
       stringOrDefault = str: def: if str == "" then def else str;
     in ''
       if [[ -h '${output}' ]]; then
@@ -227,7 +308,7 @@ utils = rec {
     + concatStringsSep
         "\n"
         (imap1 (index: name: ''
-                  secret${toString index}=$(<'${secrets.${name}}')
+                  secret${toString index}=$(<'${secrets.${name}.${attr}}')
                   export secret${toString index}
                 '')
                (attrNames secrets))
@@ -236,7 +317,7 @@ utils = rec {
     + escapeShellArg (stringOrDefault
           (concatStringsSep
             " | "
-            (imap1 (index: name: ''${name} = $ENV.secret${toString index}'')
+            (imap1 (index: name: ''${name} = ($ENV.secret${toString index}${optionalString (!secrets.${name}.quote) " | fromjson"})'')
                    (attrNames secrets)))
           ".")
     + ''
diff --git a/nixos/maintainers/scripts/azure-new/shell.nix b/nixos/maintainers/scripts/azure-new/shell.nix
index 592f1bf9056e5..9bc79cc69d7f8 100644
--- a/nixos/maintainers/scripts/azure-new/shell.nix
+++ b/nixos/maintainers/scripts/azure-new/shell.nix
@@ -1,13 +1,16 @@
-with (import ../../../../default.nix {});
-stdenv.mkDerivation {
+{
+  pkgs ? import ../../../../default.nix { },
+}:
+
+pkgs.stdenv.mkDerivation {
   name = "nixcfg-azure-devenv";
 
-  nativeBuildInputs = [
+  nativeBuildInputs = with pkgs; [
     azure-cli
     bash
     cacert
     azure-storage-azcopy
   ];
 
-  AZURE_CONFIG_DIR="/tmp/azure-cli/.azure";
+  AZURE_CONFIG_DIR = "/tmp/azure-cli/.azure";
 }
diff --git a/nixos/maintainers/scripts/ec2/amazon-image.nix b/nixos/maintainers/scripts/ec2/amazon-image.nix
index 8b6a9bc52b128..1b3724bfc170f 100644
--- a/nixos/maintainers/scripts/ec2/amazon-image.nix
+++ b/nixos/maintainers/scripts/ec2/amazon-image.nix
@@ -1,8 +1,8 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib) mkOption optionalString types versionAtLeast;
+  inherit (lib.options) literalExpression;
   cfg = config.amazonImage;
   amiBootMode = if config.ec2.efi then "uefi" else "legacy-bios";
 
@@ -15,7 +15,7 @@ in {
   # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/nvme-ebs-volumes.html#timeout-nvme-ebs-volumes
   config.boot.kernelParams =
     let timeout =
-      if pkgs.lib.versionAtLeast config.boot.kernelPackages.kernel.version "4.15"
+      if versionAtLeast config.boot.kernelPackages.kernel.version "4.15"
       then "4294967295"
       else  "255";
     in [ "nvme_core.io_timeout=${timeout}" ];
@@ -156,5 +156,5 @@ in {
     };
   in if config.ec2.zfs.enable then zfsBuilder else extBuilder;
 
-  meta.maintainers = with maintainers; [ arianvp ];
+  meta.maintainers = with lib.maintainers; [ arianvp ];
 }
diff --git a/nixos/maintainers/scripts/incus/incus-container-image-inner.nix b/nixos/maintainers/scripts/incus/incus-container-image-inner.nix
new file mode 100644
index 0000000000000..68979306a6105
--- /dev/null
+++ b/nixos/maintainers/scripts/incus/incus-container-image-inner.nix
@@ -0,0 +1,34 @@
+# 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’).
+
+{ modulesPath, ... }:
+
+{
+  imports = [
+    # Include the default incus configuration.
+    "${modulesPath}/virtualisation/lxc-container.nix"
+    # Include the container-specific autogenerated configuration.
+    ./incus.nix
+  ];
+
+  networking = {
+    dhcpcd.enable = false;
+    useDHCP = false;
+    useHostResolvConf = false;
+  };
+
+  systemd.network = {
+    enable = true;
+    networks."50-eth0" = {
+      matchConfig.Name = "eth0";
+      networkConfig = {
+        DHCP = "ipv4";
+        IPv6AcceptRA = true;
+      };
+      linkConfig.RequiredForOnline = "routable";
+    };
+  };
+
+  system.stateVersion = "@stateVersion@"; # Did you read the comment?
+}
diff --git a/nixos/maintainers/scripts/incus/incus-container-image.nix b/nixos/maintainers/scripts/incus/incus-container-image.nix
new file mode 100644
index 0000000000000..63b9353f7ee39
--- /dev/null
+++ b/nixos/maintainers/scripts/incus/incus-container-image.nix
@@ -0,0 +1,47 @@
+{ lib, pkgs, ... }:
+
+{
+  imports = [ ../../../modules/virtualisation/lxc-container.nix ];
+
+  virtualisation.lxc.templates.nix = {
+    enable = true;
+    target = "/etc/nixos/incus.nix";
+    template = ./nix.tpl;
+    when = [
+      "create"
+      "copy"
+    ];
+  };
+
+  # copy the config for nixos-rebuild
+  system.activationScripts.config =
+    let
+      config = pkgs.substituteAll {
+        src = ./incus-container-image-inner.nix;
+        stateVersion = lib.trivial.release;
+      };
+    in
+    ''
+      if [ ! -e /etc/nixos/configuration.nix ]; then
+        install -m 0644 -D ${config} /etc/nixos/configuration.nix
+      fi
+    '';
+
+  networking = {
+    dhcpcd.enable = false;
+    useDHCP = false;
+    useHostResolvConf = false;
+  };
+
+  systemd.network = {
+    enable = true;
+    networks."50-eth0" = {
+      matchConfig.Name = "eth0";
+      networkConfig = {
+        DHCP = "ipv4";
+        IPv6AcceptRA = true;
+      };
+      linkConfig.RequiredForOnline = "routable";
+    };
+  };
+}
diff --git a/nixos/maintainers/scripts/incus/incus-virtual-machine-image-inner.nix b/nixos/maintainers/scripts/incus/incus-virtual-machine-image-inner.nix
new file mode 100644
index 0000000000000..cd176a38988fe
--- /dev/null
+++ b/nixos/maintainers/scripts/incus/incus-virtual-machine-image-inner.nix
@@ -0,0 +1,34 @@
+# 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’).
+
+{ modulesPath, ... }:
+
+{
+  imports = [
+    # Include the default incus configuration.
+    "${modulesPath}/virtualisation/incus-virtual-machine.nix"
+    # Include the container-specific autogenerated configuration.
+    ./incus.nix
+  ];
+
+  networking = {
+    dhcpcd.enable = false;
+    useDHCP = false;
+    useHostResolvConf = false;
+  };
+
+  systemd.network = {
+    enable = true;
+    networks."50-enp5s0" = {
+      matchConfig.Name = "enp5s0";
+      networkConfig = {
+        DHCP = "ipv4";
+        IPv6AcceptRA = true;
+      };
+      linkConfig.RequiredForOnline = "routable";
+    };
+  };
+
+  system.stateVersion = "@stateVersion@"; # Did you read the comment?
+}
diff --git a/nixos/maintainers/scripts/incus/incus-virtual-machine-image.nix b/nixos/maintainers/scripts/incus/incus-virtual-machine-image.nix
new file mode 100644
index 0000000000000..0742e7d75ac99
--- /dev/null
+++ b/nixos/maintainers/scripts/incus/incus-virtual-machine-image.nix
@@ -0,0 +1,48 @@
+{ lib, pkgs, ... }:
+
+{
+  imports = [ ../../../modules/virtualisation/incus-virtual-machine.nix ];
+
+  virtualisation.lxc.templates.nix = {
+    enable = true;
+    target = "/etc/nixos/incus.nix";
+    template = ./nix.tpl;
+    when = [
+      "create"
+      "copy"
+    ];
+  };
+
+  # copy the config for nixos-rebuild
+  system.activationScripts.config =
+    let
+      config = pkgs.substituteAll {
+        src = ./incus-virtual-machine-image-inner.nix;
+        stateVersion = lib.trivial.release;
+      };
+    in
+    ''
+      if [ ! -e /etc/nixos/configuration.nix ]; then
+        install -m 0644 -D ${config} /etc/nixos/configuration.nix
+      fi
+    '';
+
+  # Network
+  networking = {
+    dhcpcd.enable = false;
+    useDHCP = false;
+    useHostResolvConf = false;
+  };
+
+  systemd.network = {
+    enable = true;
+    networks."50-enp5s0" = {
+      matchConfig.Name = "enp5s0";
+      networkConfig = {
+        DHCP = "ipv4";
+        IPv6AcceptRA = true;
+      };
+      linkConfig.RequiredForOnline = "routable";
+    };
+  };
+}
diff --git a/nixos/maintainers/scripts/incus/nix.tpl b/nixos/maintainers/scripts/incus/nix.tpl
new file mode 100644
index 0000000000000..38c2fc1ebffbc
--- /dev/null
+++ b/nixos/maintainers/scripts/incus/nix.tpl
@@ -0,0 +1,12 @@
+{
+  lib,
+  config,
+  pkgs,
+  ...
+}:
+
+# WARNING: THIS CONFIGURATION IS AUTOGENERATED AND WILL BE OVERWRITTEN AUTOMATICALLY
+
+{
+  networking.hostName = "{{ container.name }}";
+}
diff --git a/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix b/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
index 4698971de8ffe..5468488fafedc 100644
--- a/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
@@ -2,7 +2,7 @@
 # your system.  Help is available in the configuration.nix(5) man page
 # and in the NixOS manual (accessible by running ‘nixos-help’).
 
-{ config, pkgs, lib, modulesPath, ... }:
+{ modulesPath, ... }:
 
 {
   imports =
diff --git a/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix b/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix
index d1264fa043042..5931a561b31cc 100644
--- a/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix
+++ b/nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix
@@ -2,7 +2,7 @@
 # your system.  Help is available in the configuration.nix(5) man page
 # and in the NixOS manual (accessible by running ‘nixos-help’).
 
-{ config, pkgs, lib, modulesPath, ... }:
+{ modulesPath, ... }:
 
 {
   imports =
diff --git a/nixos/modules/config/fonts/ghostscript.nix b/nixos/modules/config/fonts/ghostscript.nix
index a5508b948990c..5db7c0ac71799 100644
--- a/nixos/modules/config/fonts/ghostscript.nix
+++ b/nixos/modules/config/fonts/ghostscript.nix
@@ -18,6 +18,6 @@ with lib;
   };
 
   config = mkIf config.fonts.enableGhostscriptFonts {
-    fonts.packages = [ "${pkgs.ghostscript}/share/ghostscript/fonts" ];
+    fonts.packages = [ pkgs.ghostscript.fonts ];
   };
 }
diff --git a/nixos/modules/config/ldap.nix b/nixos/modules/config/ldap.nix
index 7f79db8d0a60d..fd26750c273bc 100644
--- a/nixos/modules/config/ldap.nix
+++ b/nixos/modules/config/ldap.nix
@@ -1,9 +1,7 @@
 { config, lib, pkgs, ... }:
 
-with pkgs;
-with lib;
-
 let
+  inherit (lib) mkEnableOption mkIf mkMerge mkOption mkRenamedOptionModule types;
 
   cfg = config.users.ldap;
 
@@ -11,40 +9,40 @@ let
   # this file.  Directives HAVE to start in the first column!
   ldapConfig = {
     target = "ldap.conf";
-    source = writeText "ldap.conf" ''
+    source = pkgs.writeText "ldap.conf" ''
       uri ${config.users.ldap.server}
       base ${config.users.ldap.base}
       timelimit ${toString config.users.ldap.timeLimit}
       bind_timelimit ${toString config.users.ldap.bind.timeLimit}
       bind_policy ${config.users.ldap.bind.policy}
-      ${optionalString config.users.ldap.useTLS ''
+      ${lib.optionalString config.users.ldap.useTLS ''
         ssl start_tls
       ''}
-      ${optionalString (config.users.ldap.bind.distinguishedName != "") ''
+      ${lib.optionalString (config.users.ldap.bind.distinguishedName != "") ''
         binddn ${config.users.ldap.bind.distinguishedName}
       ''}
-      ${optionalString (cfg.extraConfig != "") cfg.extraConfig }
+      ${lib.optionalString (cfg.extraConfig != "") cfg.extraConfig }
     '';
   };
 
-  nslcdConfig = writeText "nslcd.conf" ''
+  nslcdConfig = pkgs.writeText "nslcd.conf" ''
     uri ${cfg.server}
     base ${cfg.base}
     timelimit ${toString cfg.timeLimit}
     bind_timelimit ${toString cfg.bind.timeLimit}
-    ${optionalString (cfg.bind.distinguishedName != "")
+    ${lib.optionalString (cfg.bind.distinguishedName != "")
       "binddn ${cfg.bind.distinguishedName}" }
-    ${optionalString (cfg.daemon.rootpwmoddn != "")
+    ${lib.optionalString (cfg.daemon.rootpwmoddn != "")
       "rootpwmoddn ${cfg.daemon.rootpwmoddn}" }
-    ${optionalString (cfg.daemon.extraConfig != "") cfg.daemon.extraConfig }
+    ${lib.optionalString (cfg.daemon.extraConfig != "") cfg.daemon.extraConfig }
   '';
 
   # nslcd normally reads configuration from /etc/nslcd.conf.
   # this file might contain secrets. We append those at runtime,
   # so redirect its location to something more temporary.
-  nslcdWrapped = runCommand "nslcd-wrapped" { nativeBuildInputs = [ makeWrapper ]; } ''
+  nslcdWrapped = pkgs.runCommand "nslcd-wrapped" { nativeBuildInputs = [ pkgs.makeWrapper ]; } ''
     mkdir -p $out/bin
-    makeWrapper ${nss_pam_ldapd}/sbin/nslcd $out/bin/nslcd \
+    makeWrapper ${pkgs.nss_pam_ldapd}/sbin/nslcd $out/bin/nslcd \
       --set LD_PRELOAD    "${pkgs.libredirect}/lib/libredirect.so" \
       --set NIX_REDIRECTS "/etc/nslcd.conf=/run/nslcd/nslcd.conf"
   '';
@@ -222,17 +220,17 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.etc = optionalAttrs (!cfg.daemon.enable) {
+    environment.etc = lib.optionalAttrs (!cfg.daemon.enable) {
       "ldap.conf" = ldapConfig;
     };
 
-    system.nssModules = mkIf cfg.nsswitch (singleton (
-      if cfg.daemon.enable then nss_pam_ldapd else nss_ldap
+    system.nssModules = mkIf cfg.nsswitch (lib.singleton (
+      if cfg.daemon.enable then pkgs.nss_pam_ldapd else pkgs.nss_ldap
     ));
 
-    system.nssDatabases.group = optional cfg.nsswitch "ldap";
-    system.nssDatabases.passwd = optional cfg.nsswitch "ldap";
-    system.nssDatabases.shadow = optional cfg.nsswitch "ldap";
+    system.nssDatabases.group = lib.optional cfg.nsswitch "ldap";
+    system.nssDatabases.passwd = lib.optional cfg.nsswitch "ldap";
+    system.nssDatabases.shadow = lib.optional cfg.nsswitch "ldap";
 
     users = mkIf cfg.daemon.enable {
       groups.nslcd = {
diff --git a/nixos/modules/config/nix-channel.nix b/nixos/modules/config/nix-channel.nix
index 6498ce6c469ca..2703a60f858fb 100644
--- a/nixos/modules/config/nix-channel.nix
+++ b/nixos/modules/config/nix-channel.nix
@@ -12,6 +12,7 @@ let
     mkDefault
     mkIf
     mkOption
+    stringAfter
     types
     ;
 
@@ -94,10 +95,11 @@ in
       NIX_PATH = cfg.nixPath;
     };
 
-    nix.settings.nix-path = mkIf (! cfg.channel.enable) (mkDefault "");
-
     systemd.tmpfiles.rules = lib.mkIf cfg.channel.enable [
       ''f /root/.nix-channels - - - - ${config.system.defaultChannel} nixos\n''
     ];
+
+    system.activationScripts.no-nix-channel = mkIf (!cfg.channel.enable)
+      (stringAfter [ "etc" "users" ] (builtins.readFile ./nix-channel/activation-check.sh));
   };
 }
diff --git a/nixos/modules/config/nix-channel/activation-check.sh b/nixos/modules/config/nix-channel/activation-check.sh
new file mode 100644
index 0000000000000..42b1b712d702b
--- /dev/null
+++ b/nixos/modules/config/nix-channel/activation-check.sh
@@ -0,0 +1,21 @@
+# shellcheck shell=bash
+
+explainChannelWarning=0
+if [[ -e "/root/.nix-defexpr/channels" ]]; then
+    warn '/root/.nix-defexpr/channels exists, but channels have been disabled.'
+    explainChannelWarning=1
+fi
+if [[ -e "/nix/var/nix/profiles/per-user/root/channels" ]]; then
+    warn "/nix/var/nix/profiles/per-user/root/channels exists, but channels have been disabled."
+    explainChannelWarning=1
+fi
+while IFS=: read -r _ _ _ _ _ home _ ; do
+    if [[ -n  "$home" && -e "$home/.nix-defexpr/channels" ]]; then
+        warn "$home/.nix-defexpr/channels exists, but channels have been disabled." 1>&2
+        explainChannelWarning=1
+    fi
+done < <(getent passwd)
+if [[ $explainChannelWarning -eq 1 ]]; then
+    echo "Due to https://github.com/NixOS/nix/issues/9574, Nix may still use these channels when NIX_PATH is unset." 1>&2
+    echo "Delete the above directory or directories to prevent this." 1>&2
+fi
diff --git a/nixos/modules/config/nix-channel/test.nix b/nixos/modules/config/nix-channel/test.nix
new file mode 100644
index 0000000000000..4b00cf9db3c47
--- /dev/null
+++ b/nixos/modules/config/nix-channel/test.nix
@@ -0,0 +1,19 @@
+# Run:
+#   nix-build -A nixosTests.nix-channel
+{ lib, testers }:
+let
+  inherit (lib) fileset;
+
+  runShellcheck = testers.shellcheck {
+    src = fileset.toSource {
+      root = ./.;
+      fileset = fileset.unions [
+        ./activation-check.sh
+      ];
+    };
+  };
+
+in
+lib.recurseIntoAttrs {
+  inherit runShellcheck;
+}
diff --git a/nixos/modules/config/nix.nix b/nixos/modules/config/nix.nix
index b5fe0a3bd1ce2..9505c60d4f630 100644
--- a/nixos/modules/config/nix.nix
+++ b/nixos/modules/config/nix.nix
@@ -302,7 +302,6 @@ in
 
             trusted-users = mkOption {
               type = types.listOf types.str;
-              default = [ "root" ];
               example = [ "root" "alice" "@wheel" ];
               description = ''
                 A list of names of users that have additional rights when
@@ -376,6 +375,7 @@ in
     environment.etc."nix/nix.conf".source = nixConf;
     nix.settings = {
       trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
+      trusted-users = [ "root" ];
       substituters = mkAfter [ "https://cache.nixos.org/" ];
       system-features = mkDefault (
         [ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index 930e57dbde5bb..2448d08a23997 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -33,7 +33,6 @@ with lib;
       fastfetch = super.fastfetch.override { vulkanSupport = false; waylandSupport = false; x11Support = false; };
       ffmpeg = super.ffmpeg.override { ffmpegVariant = "headless"; };
       ffmpeg_4 = super.ffmpeg_4.override { ffmpegVariant = "headless"; };
-      ffmpeg_5 = super.ffmpeg_5.override { ffmpegVariant = "headless"; };
       ffmpeg_6 = super.ffmpeg_6.override { ffmpegVariant = "headless"; };
       ffmpeg_7 = super.ffmpeg_7.override { ffmpegVariant = "headless"; };
       # dep of graphviz, libXpm is optional for Xpm support
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index 7c3a284e8780c..27c164a9a6dc8 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -6,7 +6,6 @@ with lib;
 let
 
   cfg = config.hardware.pulseaudio;
-  alsaCfg = config.sound;
 
   hasZeroconf = let z = cfg.zeroconf; in z.publish.enable || z.discovery.enable;
 
@@ -58,7 +57,7 @@ let
   # Write an /etc/asound.conf that causes all ALSA applications to
   # be re-routed to the PulseAudio server through ALSA's Pulse
   # plugin.
-  alsaConf = writeText "asound.conf" (''
+  alsaConf = ''
     pcm_type.pulse {
       libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
       ${lib.optionalString enable32BitAlsaPlugins
@@ -76,8 +75,7 @@ let
     ctl.!default {
       type pulse
     }
-    ${alsaCfg.extraConfig}
-  '');
+  '';
 
 in {
 
@@ -221,10 +219,8 @@ in {
 
       environment.systemPackages = [ overriddenPackage ];
 
-      sound.enable = true;
-
       environment.etc = {
-        "asound.conf".source = alsaConf;
+        "alsa/conf.d/99-pulseaudio.conf".text = alsaConf;
 
         "pulse/daemon.conf".source = writeText "daemon.conf"
           (lib.generators.toKeyValue {} cfg.daemon.config);
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index 2c19fb8a029d3..50796f8bc6f1e 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -42,8 +42,10 @@ in
         strings.  The latter is concatenated, interspersed with colon
         characters.
       '';
-      type = with types; attrsOf (oneOf [ (listOf (oneOf [ float int str ])) float int str path ]);
-      apply = mapAttrs (n: v: if isList v then concatMapStringsSep ":" toString v else toString v);
+      type = with types; attrsOf (oneOf [ (listOf (oneOf [ int str path ])) int str path ]);
+      apply = let
+        toStr = v: if isPath v then "${v}" else toString v;
+      in mapAttrs (n: v: if isList v then concatMapStringsSep ":" toStr v else toStr v);
     };
 
     environment.profiles = mkOption {
diff --git a/nixos/modules/config/stevenblack.nix b/nixos/modules/config/stevenblack.nix
index 5b85073c6908d..95f6c9e73eb3e 100644
--- a/nixos/modules/config/stevenblack.nix
+++ b/nixos/modules/config/stevenblack.nix
@@ -1,34 +1,49 @@
-{ config, lib, pkgs, ... }:
-
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 let
-  inherit (lib) optionals mkOption mkEnableOption types mkIf elem concatStringsSep maintainers;
-  cfg = config.networking.stevenblack;
+  inherit (lib)
+    getOutput
+    maintainers
+    mkEnableOption
+    mkIf
+    mkOption
+    mkPackageOption
+    types
+    ;
 
-  # needs to be in a specific order
-  activatedHosts = with cfg; [ ]
-    ++ optionals (elem "fakenews" block) [ "fakenews" ]
-    ++ optionals (elem "gambling" block) [ "gambling" ]
-    ++ optionals (elem "porn" block) [ "porn" ]
-    ++ optionals (elem "social" block) [ "social" ];
-
-  hostsPath = "${pkgs.stevenblack-blocklist}/alternates/" + concatStringsSep "-" activatedHosts + "/hosts";
+  cfg = config.networking.stevenblack;
 in
 {
   options.networking.stevenblack = {
     enable = mkEnableOption "the stevenblack hosts file blocklist";
 
+    package = mkPackageOption pkgs "stevenblack-blocklist" { };
+
     block = mkOption {
-      type = types.listOf (types.enum [ "fakenews" "gambling" "porn" "social" ]);
+      type = types.listOf (
+        types.enum [
+          "fakenews"
+          "gambling"
+          "porn"
+          "social"
+        ]
+      );
       default = [ ];
       description = "Additional blocklist extensions.";
     };
   };
 
   config = mkIf cfg.enable {
-    networking.hostFiles = [ ]
-      ++ optionals (activatedHosts != [ ]) [ hostsPath ]
-      ++ optionals (activatedHosts == [ ]) [ "${pkgs.stevenblack-blocklist}/hosts" ];
+    networking.hostFiles = map (x: "${getOutput x cfg.package}/hosts") ([ "ads" ] ++ cfg.block);
   };
 
-  meta.maintainers = [ maintainers.moni maintainers.artturin ];
+  meta.maintainers = with maintainers; [
+    moni
+    artturin
+    frontear
+  ];
 }
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
index 53aea5d847129..e945e18b1f258 100644
--- a/nixos/modules/config/swap.nix
+++ b/nixos/modules/config/swap.nix
@@ -1,9 +1,7 @@
 { config, lib, pkgs, utils, ... }:
 
-with utils;
-with lib;
-
 let
+  inherit (lib) mkIf mkOption types;
 
   randomEncryptionCoerce = enable: { inherit enable; };
 
@@ -188,7 +186,7 @@ let
     config = {
       device = mkIf options.label.isDefined
         "/dev/disk/by-label/${config.label}";
-      deviceName = lib.replaceStrings ["\\"] [""] (escapeSystemdPath config.device);
+      deviceName = lib.replaceStrings ["\\"] [""] (utils.escapeSystemdPath config.device);
       realDevice = if config.randomEncryption.enable then "/dev/mapper/${config.deviceName}" else config.device;
     };
 
@@ -224,8 +222,8 @@ in
 
   };
 
-  config = mkIf ((length config.swapDevices) != 0) {
-    assertions = map (sw: {
+  config = mkIf ((lib.length config.swapDevices) != 0) {
+    assertions = lib.map (sw: {
       assertion = sw.randomEncryption.enable -> builtins.match "/dev/disk/by-(uuid|label)/.*" sw.device == null;
       message = ''
         You cannot use swap device "${sw.device}" with randomEncryption enabled.
@@ -235,22 +233,22 @@ in
     }) config.swapDevices;
 
     warnings =
-      concatMap (sw:
-        if sw.size != null && hasPrefix "/dev/" sw.device
+      lib.concatMap (sw:
+        if sw.size != null && lib.hasPrefix "/dev/" sw.device
         then [ "Setting the swap size of block device ${sw.device} has no effect" ]
         else [ ])
       config.swapDevices;
 
-    system.requiredKernelConfig = with config.lib.kernelConfig; [
-      (isYes "SWAP")
+    system.requiredKernelConfig = [
+      (config.lib.kernelConfig.isYes "SWAP")
     ];
 
     # Create missing swapfiles.
     systemd.services =
       let
         createSwapDevice = sw:
-          let realDevice' = escapeSystemdPath sw.realDevice;
-          in nameValuePair "mkswap-${sw.deviceName}"
+          let realDevice' = utils.escapeSystemdPath sw.realDevice;
+          in lib.nameValuePair "mkswap-${sw.deviceName}"
           { description = "Initialisation of swap device ${sw.device}";
             # The mkswap service fails for file-backed swap devices if the
             # loop module has not been loaded before the service runs.
@@ -261,29 +259,30 @@ in
             before = [ "${realDevice'}.swap" "shutdown.target"];
             conflicts = [ "shutdown.target" ];
             path = [ pkgs.util-linux pkgs.e2fsprogs ]
-              ++ optional sw.randomEncryption.enable pkgs.cryptsetup;
+              ++ lib.optional sw.randomEncryption.enable pkgs.cryptsetup;
 
             environment.DEVICE = sw.device;
 
             script =
               ''
-                ${optionalString (sw.size != null) ''
+                ${lib.optionalString (sw.size != null) ''
                   currentSize=$(( $(stat -c "%s" "$DEVICE" 2>/dev/null || echo 0) / 1024 / 1024 ))
                   if [[ ! -b "$DEVICE" && "${toString sw.size}" != "$currentSize" ]]; then
                     # Disable CoW for CoW based filesystems like BTRFS.
                     truncate --size 0 "$DEVICE"
                     chattr +C "$DEVICE" 2>/dev/null || true
 
-                    dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size}
-                    ${optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
+                    echo "Creating swap file using dd and mkswap."
+                    dd if=/dev/zero of="$DEVICE" bs=1M count=${toString sw.size} status=progress
+                    ${lib.optionalString (!sw.randomEncryption.enable) "mkswap ${sw.realDevice}"}
                   fi
                 ''}
-                ${optionalString sw.randomEncryption.enable ''
+                ${lib.optionalString sw.randomEncryption.enable ''
                   cryptsetup plainOpen -c ${sw.randomEncryption.cipher} -d ${sw.randomEncryption.source} \
-                  ${concatStringsSep " \\\n" (flatten [
-                    (optional (sw.randomEncryption.sectorSize != null) "--sector-size=${toString sw.randomEncryption.sectorSize}")
-                    (optional (sw.randomEncryption.keySize != null) "--key-size=${toString sw.randomEncryption.keySize}")
-                    (optional sw.randomEncryption.allowDiscards "--allow-discards")
+                  ${lib.concatStringsSep " \\\n" (lib.flatten [
+                    (lib.optional (sw.randomEncryption.sectorSize != null) "--sector-size=${toString sw.randomEncryption.sectorSize}")
+                    (lib.optional (sw.randomEncryption.keySize != null) "--key-size=${toString sw.randomEncryption.keySize}")
+                    (lib.optional sw.randomEncryption.allowDiscards "--allow-discards")
                   ])} ${sw.device} ${sw.deviceName}
                   mkswap ${sw.realDevice}
                 ''}
@@ -295,12 +294,12 @@ in
               Type = "oneshot";
               RemainAfterExit = sw.randomEncryption.enable;
               UMask = "0177";
-              ExecStop = optionalString sw.randomEncryption.enable "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}";
+              ExecStop = lib.optionalString sw.randomEncryption.enable "${pkgs.cryptsetup}/bin/cryptsetup luksClose ${sw.deviceName}";
             };
             restartIfChanged = false;
           };
 
-      in listToAttrs (map createSwapDevice (filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices));
+      in lib.listToAttrs (lib.map createSwapDevice (lib.filter (sw: sw.size != null || sw.randomEncryption.enable) config.swapDevices));
 
   };
 
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index 562100ad6201c..21280d023a4ce 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -153,10 +153,8 @@ in
         "/sbin"
         "/share/emacs"
         "/share/hunspell"
-        "/share/nano"
         "/share/org"
         "/share/themes"
-        "/share/vim-plugins"
         "/share/vulkan"
         "/share/kservices5"
         "/share/kservicetypes5"
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index 7c6851473f42f..f0b692a759d1a 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -234,7 +234,7 @@ foreach my $u (@{$spec->{users}}) {
 
     # Ensure home directory incl. ownership and permissions.
     if ($u->{createHome} and !$is_dry) {
-        make_path($u->{home}, { mode => oct($u->{homeMode}) }) if ! -e $u->{home};
+        make_path($u->{home}, { mode => 0755 }) if ! -e $u->{home};
         chown $u->{uid}, $u->{gid}, $u->{home};
         chmod oct($u->{homeMode}), $u->{home};
     }
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 3ef8993fa665b..69646e550f1f3 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -1,8 +1,43 @@
 { config, lib, utils, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    any
+    attrNames
+    attrValues
+    concatMap
+    concatStrings
+    elem
+    filter
+    filterAttrs
+    flatten
+    flip
+    foldr
+    getAttr
+    hasAttr
+    id
+    length
+    listToAttrs
+    literalExpression
+    mapAttrs'
+    mapAttrsToList
+    match
+    mkAliasOptionModuleMD
+    mkDefault
+    mkIf
+    mkMerge
+    mkOption
+    mkRenamedOptionModule
+    optional
+    optionals
+    sort
+    stringAfter
+    stringLength
+    trace
+    types
+    xor
+    ;
+
   ids = config.ids;
   cfg = config.users;
 
@@ -55,7 +90,7 @@ let
 
       name = mkOption {
         type = types.passwdEntry types.str;
-        apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
+        apply = x: assert (stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
         description = ''
           The name of the user account. If undefined, the name of the
           attribute set will be used.
@@ -113,7 +148,7 @@ let
 
       group = mkOption {
         type = types.str;
-        apply = x: assert (builtins.stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
+        apply = x: assert (stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
         default = "";
         description = "The user's primary group.";
       };
@@ -462,13 +497,13 @@ let
 
   idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }:
     let
-      id = builtins.toString (builtins.getAttr idAttr (builtins.getAttr name set));
-      exists = builtins.hasAttr id acc;
-      newAcc = acc // (builtins.listToAttrs [ { name = id; value = true; } ]);
+      id = toString (getAttr idAttr (getAttr name set));
+      exists = hasAttr id acc;
+      newAcc = acc // (listToAttrs [ { name = id; value = true; } ]);
     in if dup then args else if exists
-      then builtins.trace "Duplicate ${idAttr} ${id}" { dup = true; acc = null; }
+      then trace "Duplicate ${idAttr} ${id}" { dup = true; acc = null; }
       else { dup = false; acc = newAcc; }
-    ) { dup = false; acc = {}; } (builtins.attrNames set)).dup;
+    ) { dup = false; acc = {}; } (attrNames set)).dup;
 
   uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid";
   gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
@@ -696,7 +731,7 @@ in {
       '';
     } else ""; # keep around for backwards compatibility
 
-    systemd.services.linger-users = lib.mkIf ((builtins.length lingeringUsers) > 0) {
+    systemd.services.linger-users = lib.mkIf ((length lingeringUsers) > 0) {
       wantedBy = ["multi-user.target"];
       after = ["systemd-logind.service"];
       requires = ["systemd-logind.service"];
@@ -862,7 +897,7 @@ in {
       [
         {
         assertion = (user.hashedPassword != null)
-        -> (builtins.match ".*:.*" user.hashedPassword == null);
+        -> (match ".*:.*" user.hashedPassword == null);
         message = ''
             The password hash of user "${user.name}" contains a ":" character.
             This is invalid and would break the login system because the fields
@@ -927,7 +962,7 @@ in {
         given above which can lead to surprising results. To resolve this warning,
         set at most one of the options above to a non-`null` value.
       '')
-      ++ builtins.filter (x: x != null) (
+      ++ filter (x: x != null) (
         flip mapAttrsToList cfg.users (_: user:
         # This regex matches a subset of the Modular Crypto Format (MCF)[1]
         # informal standard. Since this depends largely on the OS or the
@@ -950,7 +985,7 @@ in {
         in
         if (allowsLogin user.hashedPassword
             && user.hashedPassword != ""  # login without password
-            && builtins.match mcf user.hashedPassword == null)
+            && match mcf user.hashedPassword == null)
         then ''
           The password hash of user "${user.name}" may be invalid. You must set a
           valid hash or the user will be locked out of their account. Please
diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
index 2368ca04a49ea..ec4e13169fa38 100644
--- a/nixos/modules/config/xdg/portal.nix
+++ b/nixos/modules/config/xdg/portal.nix
@@ -6,6 +6,7 @@ let
     mkIf
     mkOption
     mkRenamedOptionModule
+    mkRemovedOptionModule
     teams
     types;
 
@@ -17,18 +18,7 @@ in
 {
   imports = [
     (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ])
-
-    ({ config, lib, options, ... }:
-      let
-        from = [ "xdg" "portal" "gtkUsePortal" ];
-        fromOpt = lib.getAttrFromPath from options;
-      in
-      {
-        warnings = lib.mkIf config.xdg.portal.gtkUsePortal [
-          "The option `${lib.showOption from}' defined in ${lib.showFiles fromOpt.files} has been deprecated. Setting the variable globally with `environment.sessionVariables' NixOS option can have unforeseen side-effects."
-        ];
-      }
-    )
+    (mkRemovedOptionModule [ "xdg" "portal" "gtkUsePortal" ] "This option has been removed due to being unsupported and discouraged by the GTK developers.")
   ];
 
   meta = {
@@ -54,18 +44,6 @@ in
       '';
     };
 
-    gtkUsePortal = mkOption {
-      type = types.bool;
-      visible = false;
-      default = false;
-      description = ''
-        Sets environment variable `GTK_USE_PORTAL` to `1`.
-        This will force GTK-based programs ran outside Flatpak to respect and use XDG Desktop Portals
-        for features like file chooser but it is an unsupported hack that can easily break things.
-        Defaults to `false` to respect its opt-in nature.
-      '';
-    };
-
     xdgOpenUsePortal = mkOption {
       type = types.bool;
       default = false;
@@ -154,7 +132,6 @@ in
         ];
 
         sessionVariables = {
-          GTK_USE_PORTAL = mkIf cfg.gtkUsePortal "1";
           NIXOS_XDG_OPEN_USE_PORTAL = mkIf cfg.xdgOpenUsePortal "1";
           NIX_XDG_DESKTOP_PORTAL_DIR = "/run/current-system/sw/share/xdg-desktop-portal/portals";
         };
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index a97c8c418c865..abad6e08611f0 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -43,7 +43,6 @@ in {
         rtl8192su-firmware
         rt5677-firmware
         rtl8761b-firmware
-        rtw88-firmware
         zd1211fw
         alsa-firmware
         sof-firmware
diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix
index 65e73833a7599..1fb97c16d75f2 100644
--- a/nixos/modules/hardware/ckb-next.nix
+++ b/nixos/modules/hardware/ckb-next.nix
@@ -41,6 +41,6 @@ in
     };
 
     meta = {
-      maintainers = with lib.maintainers; [ ];
+      maintainers = [ ];
     };
   }
diff --git a/nixos/modules/hardware/decklink.nix b/nixos/modules/hardware/decklink.nix
new file mode 100644
index 0000000000000..ca0ed389f8cc4
--- /dev/null
+++ b/nixos/modules/hardware/decklink.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.hardware.decklink;
+  kernelPackages = config.boot.kernelPackages;
+in
+{
+  options.hardware.decklink.enable = lib.mkEnableOption "hardware support for the Blackmagic Design Decklink audio/video interfaces";
+
+  config = lib.mkIf cfg.enable {
+    # snd_blackmagic-io can cause issues with pipewire,
+    # so we do not enable it by default
+    boot.kernelModules = [ "blackmagic" "blackmagic-io" ];
+    boot.extraModulePackages = [ kernelPackages.decklink ];
+    systemd.packages = [ pkgs.blackmagic-desktop-video ];
+    systemd.services.DesktopVideoHelper.wantedBy = [ "multi-user.target" ];
+  };
+}
diff --git a/nixos/modules/hardware/network/eg25-manager.nix b/nixos/modules/hardware/network/eg25-manager.nix
new file mode 100644
index 0000000000000..485c1da68f0aa
--- /dev/null
+++ b/nixos/modules/hardware/network/eg25-manager.nix
@@ -0,0 +1,27 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  inherit (lib) mkEnableOption mkIf mkPackageOption;
+  cfg = config.services.eg25-manager;
+in
+{
+  options.services.eg25-manager = {
+    enable = mkEnableOption "Quectel EG25 modem manager service";
+
+    package = mkPackageOption pkgs "eg25-manager" { };
+  };
+  config = mkIf cfg.enable {
+    systemd.packages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+    systemd.services.eg25-manager.wantedBy = [ "multi-user.target" ];
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ Luflosi ];
+  };
+}
diff --git a/nixos/modules/hardware/raid/hpsa.nix b/nixos/modules/hardware/raid/hpsa.nix
index 120348a74bfbe..873a2db380919 100644
--- a/nixos/modules/hardware/raid/hpsa.nix
+++ b/nixos/modules/hardware/raid/hpsa.nix
@@ -40,7 +40,7 @@ let
       homepage = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/";
       license = licenses.unfreeRedistributable;
       platforms = [ "x86_64-linux" ];
-      maintainers = with maintainers; [ ];
+      maintainers = [ ];
     };
   };
 in {
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index e38050e637b1c..ee8904da8572f 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -96,8 +96,15 @@ in
         Enabling this fixes screen tearing when using Optimus via PRIME (see
         {option}`hardware.nvidia.prime.sync.enable`. This is not enabled
         by default because it is not officially supported by NVIDIA and would not
-        work with SLI
-      '';
+        work with SLI.
+
+        Enabling this and using version 545 or newer of the proprietary NVIDIA
+        driver causes it to provide its own framebuffer device, which can cause
+        Wayland compositors to work when they otherwise wouldn't.
+      '' // {
+        default = lib.versionAtLeast cfg.package.version "535";
+        defaultText = lib.literalExpression "lib.versionAtLeast cfg.package.version \"535\"";
+      };
 
       prime.nvidiaBusId = lib.mkOption {
         type = busIDType;
@@ -249,7 +256,9 @@ in
 
       open = lib.mkEnableOption ''
         the open source NVIDIA kernel module
-      '';
+      '' // {
+        defaultText = lib.literalExpression ''lib.versionAtLeast config.hardware.nvidia.package.version "560"'';
+      };
     };
   };
 
@@ -298,6 +307,8 @@ in
             extraPackages32 = [ nvidia_x11.lib32 ];
           };
           environment.systemPackages = [ nvidia_x11.bin ];
+
+          hardware.nvidia.open = lib.mkDefault (lib.versionAtLeast nvidia_x11.version "560");
         })
 
         # X11
@@ -465,7 +476,6 @@ in
 
           hardware.graphics = {
             extraPackages = [ pkgs.nvidia-vaapi-driver ];
-            extraPackages32 = [ pkgs.pkgsi686Linux.nvidia-vaapi-driver ];
           };
 
           environment.systemPackages =
@@ -562,15 +572,21 @@ in
           boot = {
             extraModulePackages = if cfg.open then [ nvidia_x11.open ] else [ nvidia_x11.bin ];
             # nvidia-uvm is required by CUDA applications.
-            kernelModules = lib.optionals config.services.xserver.enable [
-              "nvidia"
-              "nvidia_modeset"
-              "nvidia_drm"
-            ];
-
-            # If requested enable modesetting via kernel parameter.
+            kernelModules =
+              lib.optionals config.services.xserver.enable [
+                "nvidia"
+                "nvidia_modeset"
+                "nvidia_drm"
+              ]
+              # With the open driver, nvidia-uvm does not automatically load as
+              # a softdep of the nvidia module, so we explicitly load it for now.
+              # See https://github.com/NixOS/nixpkgs/issues/334180
+              ++ lib.optionals (config.services.xserver.enable && cfg.open) [ "nvidia_uvm" ];
+
+            # If requested enable modesetting via kernel parameters.
             kernelParams =
               lib.optional (offloadCfg.enable || cfg.modesetting.enable) "nvidia-drm.modeset=1"
+              ++ lib.optional ((offloadCfg.enable || cfg.modesetting.enable) && lib.versionAtLeast nvidia_x11.version "545") "nvidia-drm.fbdev=1"
               ++ lib.optional cfg.powerManagement.enable "nvidia.NVreg_PreserveVideoMemoryAllocations=1"
               ++ lib.optional cfg.open "nvidia.NVreg_OpenRmEnableUnsupportedGpus=1"
               ++ lib.optional (config.boot.kernelPackages.kernel.kernelAtLeast "6.2" && !ibtSupport) "ibt=off";
diff --git a/nixos/modules/hardware/video/webcam/ipu6.nix b/nixos/modules/hardware/video/webcam/ipu6.nix
index de47fe3f6b5af..ae54e24ee2daa 100644
--- a/nixos/modules/hardware/video/webcam/ipu6.nix
+++ b/nixos/modules/hardware/video/webcam/ipu6.nix
@@ -26,9 +26,9 @@ in
 
   config = mkIf cfg.enable {
 
-    boot.extraModulePackages = with config.boot.kernelPackages; [
-      ipu6-drivers
-    ];
+    # Module is upstream as of 6.10
+    boot.extraModulePackages = with config.boot.kernelPackages;
+      optional (kernelOlder "6.10") ipu6-drivers;
 
     hardware.firmware = with pkgs; [
       ipu6-camera-bins
diff --git a/nixos/modules/i18n/input-method/default.md b/nixos/modules/i18n/input-method/default.md
index 6d12462b788e4..8101717a4da7b 100644
--- a/nixos/modules/i18n/input-method/default.md
+++ b/nixos/modules/i18n/input-method/default.md
@@ -25,7 +25,8 @@ The following snippet can be used to configure IBus:
 ```nix
 {
   i18n.inputMethod = {
-    enabled = "ibus";
+    enable = true;
+    type = "ibus";
     ibus.engines = with pkgs.ibus-engines; [ anthy hangul mozc ];
   };
 }
@@ -81,7 +82,8 @@ The following snippet can be used to configure Fcitx:
 ```nix
 {
   i18n.inputMethod = {
-    enabled = "fcitx5";
+    enable = true;
+    type = "fcitx5";
     fcitx5.addons = with pkgs; [ fcitx5-mozc fcitx5-hangul fcitx5-m17n ];
   };
 }
@@ -119,7 +121,8 @@ The following snippet can be used to configure Nabi:
 ```nix
 {
   i18n.inputMethod = {
-    enabled = "nabi";
+    enable = true;
+    type = "nabi";
   };
 }
 ```
@@ -134,7 +137,8 @@ The following snippet can be used to configure uim:
 ```nix
 {
   i18n.inputMethod = {
-    enabled = "uim";
+    enable = true;
+    type = "uim";
   };
 }
 ```
@@ -154,7 +158,8 @@ The following snippet can be used to configure Hime:
 ```nix
 {
   i18n.inputMethod = {
-    enabled = "hime";
+    enable = true;
+    type = "hime";
   };
 }
 ```
@@ -168,7 +173,8 @@ The following snippet can be used to configure Kime:
 ```nix
 {
   i18n.inputMethod = {
-    enabled = "kime";
+    enable = true;
+    type = "kime";
   };
 }
 ```
diff --git a/nixos/modules/i18n/input-method/default.nix b/nixos/modules/i18n/input-method/default.nix
index 3b439c4231b3f..15125ceb4a2d9 100644
--- a/nixos/modules/i18n/input-method/default.nix
+++ b/nixos/modules/i18n/input-method/default.nix
@@ -4,6 +4,8 @@ with lib;
 let
   cfg = config.i18n.inputMethod;
 
+  allowedTypes = types.enum [ "ibus" "fcitx5" "nabi" "uim" "hime" "kime" ];
+
   gtk2_cache = pkgs.runCommand "gtk2-immodule.cache"
     { preferLocalBuild = true;
       allowSubstitutes = false;
@@ -28,10 +30,23 @@ in
 {
   options.i18n = {
     inputMethod = {
+      enable = mkEnableOption "an additional input method type" // {
+        default = cfg.enabled != null;
+        defaultText = literalMD "`true` if the deprecated option `enabled` is set, false otherwise";
+      };
+
       enabled = mkOption {
-        type    = types.nullOr (types.enum [ "ibus" "fcitx5" "nabi" "uim" "hime" "kime" ]);
+        type    = types.nullOr allowedTypes;
         default = null;
         example = "fcitx5";
+        description = "Deprecated - use `type` and `enable = true` instead";
+      };
+
+      type = mkOption {
+        type    = types.nullOr allowedTypes;
+        default = cfg.enabled;
+        defaultText = literalMD "The value of the deprecated option `enabled`, defaulting to null";
+        example = "fcitx5";
         description = ''
           Select the enabled input method. Input methods is a software to input symbols that are not available on standard input devices.
 
@@ -59,7 +74,8 @@ in
     };
   };
 
-  config = mkIf (cfg.enabled != null) {
+  config = mkIf cfg.enable {
+    warnings = optional (cfg.enabled != null) "i18n.inputMethod.enabled will be removed in a future release. Please use .type, and .enable = true instead";
     environment.systemPackages = [ cfg.package gtk2_cache gtk3_cache ];
   };
 
diff --git a/nixos/modules/i18n/input-method/fcitx5.nix b/nixos/modules/i18n/input-method/fcitx5.nix
index bb6661e248f22..2678c4a39e4e2 100644
--- a/nixos/modules/i18n/input-method/fcitx5.nix
+++ b/nixos/modules/i18n/input-method/fcitx5.nix
@@ -3,8 +3,8 @@
 with lib;
 
 let
-  im = config.i18n.inputMethod;
-  cfg = im.fcitx5;
+  imcfg = config.i18n.inputMethod;
+  cfg = imcfg.fcitx5;
   fcitx5Package =
     if cfg.plasma6Support
     then pkgs.qt6Packages.fcitx5-with-addons.override { inherit (cfg) addons; }
@@ -108,7 +108,7 @@ in
     '')
   ];
 
-  config = mkIf (im.enabled == "fcitx5") {
+  config = mkIf (imcfg.enable && imcfg.type == "fcitx5") {
     i18n.inputMethod.package = fcitx5Package;
 
     i18n.inputMethod.fcitx5.addons = lib.optionals (cfg.quickPhrase != { }) [
diff --git a/nixos/modules/i18n/input-method/hime.nix b/nixos/modules/i18n/input-method/hime.nix
index 8482130db3e34..baf455bd2366a 100644
--- a/nixos/modules/i18n/input-method/hime.nix
+++ b/nixos/modules/i18n/input-method/hime.nix
@@ -1,8 +1,12 @@
 { config, pkgs, lib, ... }:
 
 with lib;
+
+let
+  imcfg = config.i18n.inputMethod;
+in
 {
-  config = mkIf (config.i18n.inputMethod.enabled == "hime") {
+  config = mkIf (imcfg.enable && imcfg.type == "hime") {
     i18n.inputMethod.package = pkgs.hime;
     environment.variables = {
       GTK_IM_MODULE = "hime";
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index c82f0099253b8..4af848c720150 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -3,10 +3,12 @@
 with lib;
 
 let
-  cfg = config.i18n.inputMethod.ibus;
+  imcfg = config.i18n.inputMethod;
+  cfg = imcfg.ibus;
   ibusPackage = pkgs.ibus-with-plugins.override { plugins = cfg.engines; };
-  ibusEngine = types.package // {
+  ibusEngine = lib.types.mkOptionType {
     name  = "ibus-engine";
+    inherit (lib.types.package) descriptionClass merge;
     check = x: (lib.types.package.check x) && (attrByPath ["meta" "isIbusEngine"] false x);
   };
 
@@ -52,7 +54,7 @@ in
     };
   };
 
-  config = mkIf (config.i18n.inputMethod.enabled == "ibus") {
+  config = mkIf (imcfg.enable && imcfg.type == "ibus") {
     i18n.inputMethod.package = ibusPackage;
 
     environment.systemPackages = [
diff --git a/nixos/modules/i18n/input-method/kime.nix b/nixos/modules/i18n/input-method/kime.nix
index 1fea3aeccf0a5..78f5698a80545 100644
--- a/nixos/modules/i18n/input-method/kime.nix
+++ b/nixos/modules/i18n/input-method/kime.nix
@@ -1,5 +1,6 @@
 { config, pkgs, lib, generators, ... }:
-let imcfg = config.i18n.inputMethod;
+let
+  imcfg = config.i18n.inputMethod;
 in {
   imports = [
     (lib.mkRemovedOptionModule [ "i18n" "inputMethod" "kime" "config" ] "Use i18n.inputMethod.kime.* instead")
@@ -31,7 +32,7 @@ in {
     };
   };
 
-  config = lib.mkIf (imcfg.enabled == "kime") {
+  config = lib.mkIf (imcfg.enable && imcfg.type == "kime") {
     i18n.inputMethod.package = pkgs.kime;
 
     environment.variables = {
diff --git a/nixos/modules/i18n/input-method/nabi.nix b/nixos/modules/i18n/input-method/nabi.nix
index 87620ae4e7b2b..0eb9a7c825c88 100644
--- a/nixos/modules/i18n/input-method/nabi.nix
+++ b/nixos/modules/i18n/input-method/nabi.nix
@@ -1,8 +1,11 @@
 { config, pkgs, lib, ... }:
 
 with lib;
+let
+  imcfg = config.i18n.inputMethod;
+in
 {
-  config = mkIf (config.i18n.inputMethod.enabled == "nabi") {
+  config = mkIf (imcfg.enable && imcfg.type == "nabi") {
     i18n.inputMethod.package = pkgs.nabi;
 
     environment.variables = {
diff --git a/nixos/modules/i18n/input-method/uim.nix b/nixos/modules/i18n/input-method/uim.nix
index 6a636a771c1f2..7517dead6b054 100644
--- a/nixos/modules/i18n/input-method/uim.nix
+++ b/nixos/modules/i18n/input-method/uim.nix
@@ -3,7 +3,8 @@
 with lib;
 
 let
-  cfg = config.i18n.inputMethod.uim;
+  imcfg = config.i18n.inputMethod;
+  cfg = imcfg.uim;
 in
 {
   options = {
@@ -21,7 +22,7 @@ in
 
   };
 
-  config = mkIf (config.i18n.inputMethod.enabled == "uim") {
+  config = mkIf (imcfg.enable && imcfg.type == "uim") {
     i18n.inputMethod.package = pkgs.uim;
 
     environment.variables = {
diff --git a/nixos/modules/image/repart-image.nix b/nixos/modules/image/repart-image.nix
index de03beeafc0b7..41f68a0282ac9 100644
--- a/nixos/modules/image/repart-image.nix
+++ b/nixos/modules/image/repart-image.nix
@@ -69,7 +69,7 @@ let
     patchShebangs --build $out
 
     black --check --diff $out
-    ruff --line-length 88 $out
+    ruff check --line-length 88 $out
     mypy --strict $out
   '';
 
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index 93f806b75eb11..33301c6423db9 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -88,8 +88,8 @@ with lib;
       prepend = [ "${config.system.build.initialRamdisk}/initrd" ];
 
       contents =
-        [ { object = config.system.build.squashfsStore;
-            symlink = "/nix-store.squashfs";
+        [ { source = config.system.build.squashfsStore;
+            target = "/nix-store.squashfs";
           }
         ];
     };
diff --git a/nixos/modules/installer/sd-card/sd-image-aarch64.nix b/nixos/modules/installer/sd-card/sd-image-aarch64.nix
index cf01005fdc8a9..0bb4f5ab807dd 100644
--- a/nixos/modules/installer/sd-card/sd-image-aarch64.nix
+++ b/nixos/modules/installer/sd-card/sd-image-aarch64.nix
@@ -66,6 +66,12 @@
 
         # Add pi3 specific files
         cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin firmware/u-boot-rpi3.bin
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-2-b.dtb firmware/
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-3-b.dtb firmware/
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-3-b-plus.dtb firmware/
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-cm3.dtb firmware/
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-zero-2.dtb firmware/
+        cp ${pkgs.raspberrypifw}/share/raspberrypi/boot/bcm2710-rpi-zero-2-w.dtb firmware/
 
         # Add pi4 specific files
         cp ${pkgs.ubootRaspberryPi4_64bit}/u-boot.bin firmware/u-boot-rpi4.bin
diff --git a/nixos/modules/installer/tools/manpages/nixos-install.8 b/nixos/modules/installer/tools/manpages/nixos-install.8
index c6c8ed15224d3..e1af90a37d76b 100644
--- a/nixos/modules/installer/tools/manpages/nixos-install.8
+++ b/nixos/modules/installer/tools/manpages/nixos-install.8
@@ -14,6 +14,8 @@
 .Op Fl -root Ar root
 .Op Fl -system Ar path
 .Op Fl -flake Ar flake-uri
+.Op Fl -file | f Ar path
+.Op Fl -attr | A Ar attrPath
 .Op Fl -impure
 .Op Fl -channel Ar channel
 .Op Fl -no-channel-copy
@@ -111,6 +113,32 @@ output named
 .Ql nixosConfigurations. Ns Ar name Ns
 \&.
 .
+.It Fl -file Ar path , Fl f Ar path
+Enable and build the NixOS system from the specified file. The file must
+evaluate to an attribute set, and it must contain a valid NixOS configuration
+at attribute
+.Va attrPath Ns
+\&. This is useful for building a NixOS system from a nix file that is not
+a flake or a NixOS configuration module. Attribute set a with valid NixOS
+configuration can be made using
+.Va nixos
+function in nixpkgs or importing and calling
+.Pa nixos/lib/eval-config.nix
+from nixpkgs. If specified without
+.Fl -attr
+option, builds the configuration from the top-level
+attribute of the file.
+.
+.It Fl -attr Ar attrPath , Fl A Ar attrPath
+Enable and build the NixOS system from nix file and use the specified attribute
+path from file specified by the
+.Fl -file
+option. If specified without
+.Fl -file
+option, uses
+.Va [root] Ns Pa /etc/nixos/default.nix Ns
+\&.
+.
 .It Fl -channel Ar channel
 If this option is provided, do not copy the current
 .Dq nixos
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index 54d3a107d6276..f9ea7eb395973 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,7 +1,7 @@
 {
-  x86_64-linux = "/nix/store/1w4b47zhp33md29wjhgg549pc281vv02-nix-2.18.4";
-  i686-linux = "/nix/store/hz02kn0ffn3wdi2xs7lndpr88v4v4fp2-nix-2.18.4";
-  aarch64-linux = "/nix/store/90zwqa9z2fgldc7ki1p5gfvglchjh9r6-nix-2.18.4";
-  x86_64-darwin = "/nix/store/bd1ix5mj9lj2yh7bqnmdjc24zlg5jivk-nix-2.18.4";
-  aarch64-darwin = "/nix/store/5hvsmklhqiay5i4q5vdkg60p8qpc69rz-nix-2.18.4";
+  x86_64-linux = "/nix/store/f409bhlpp0xkzvdz95qr2yvfjfi8r9jc-nix-2.18.5";
+  i686-linux = "/nix/store/ra39jzrxq3bcpf55aahwv5037akvylf5-nix-2.18.5";
+  aarch64-linux = "/nix/store/xiw8a4jbnw18svgdb04hyqzg5bsjspqf-nix-2.18.5";
+  x86_64-darwin = "/nix/store/k2gzx7i90x3h2c8g6xdi1jkwbl6ic895-nix-2.18.5";
+  aarch64-darwin = "/nix/store/rqwymbndaqxma6p8s5brcl9k32n5xx54-nix-2.18.5";
 }
diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh
index 4e42875c03650..a76b4ffb44444 100755
--- a/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixos/modules/installer/tools/nixos-install.sh
@@ -17,6 +17,9 @@ mountPoint=/mnt
 channelPath=
 system=
 verbosity=()
+attr=
+buildFile=
+buildingAttribute=1
 
 while [ "$#" -gt 0 ]; do
     i="$1"; shift 1
@@ -41,6 +44,24 @@ while [ "$#" -gt 0 ]; do
           flakeFlags=(--experimental-features 'nix-command flakes')
           shift 1
           ;;
+        --file|-f)
+            if [ -z "$1" ]; then
+                log "$0: '$i' requires an argument"
+                exit 1
+            fi
+            buildFile="$1"
+            buildingAttribute=
+            shift 1
+            ;;
+        --attr|-A)
+            if [ -z "$1" ]; then
+                log "$0: '$i' requires an argument"
+                exit 1
+            fi
+            attr="$1"
+            buildingAttribute=
+            shift 1
+            ;;
         --recreate-lock-file|--no-update-lock-file|--no-write-lock-file|--no-registries|--commit-lock-file)
           lockFlags+=("$i")
           ;;
@@ -101,17 +122,30 @@ while [[ "$checkPath" != "/" ]]; do
     checkPath="$(dirname "$checkPath")"
 done
 
-# Get the path of the NixOS configuration file.
-if [[ -z $NIXOS_CONFIG ]]; then
-    NIXOS_CONFIG=$mountPoint/etc/nixos/configuration.nix
-fi
-
-if [[ ${NIXOS_CONFIG:0:1} != / ]]; then
-    echo "$0: \$NIXOS_CONFIG is not an absolute path"
+# Verify that user is not trying to use attribute building and flake
+# at the same time
+if [[ -z $buildingAttribute && -n $flake ]]; then
+    echo "$0: '--flake' cannot be used with '--file' or '--attr'"
     exit 1
 fi
 
-if [[ -n $flake ]]; then
+# Get the path of the NixOS configuration file.
+if [[ -z $flake && -n $buildingAttribute ]]; then
+    if [[ -z $NIXOS_CONFIG ]]; then
+        NIXOS_CONFIG=$mountPoint/etc/nixos/configuration.nix
+    fi
+
+    if [[ ${NIXOS_CONFIG:0:1} != / ]]; then
+        echo "$0: \$NIXOS_CONFIG is not an absolute path"
+        exit 1
+    fi
+elif [[ -z $buildingAttribute ]]; then
+    if [[ -z $buildFile ]]; then
+        buildFile="$mountPoint/etc/nixos/default.nix"
+    elif [[ -d $buildFile ]]; then
+        buildFile="$buildFile/default.nix"
+    fi
+elif [[ -n $flake ]]; then
     if [[ $flake =~ ^(.*)\#([^\#\"]*)$ ]]; then
        flake="${BASH_REMATCH[1]}"
        flakeAttr="${BASH_REMATCH[2]}"
@@ -129,11 +163,16 @@ if [[ -n $flake ]]; then
     flake=$(nix "${flakeFlags[@]}" flake metadata --json "${extraBuildFlags[@]}" "${lockFlags[@]}" -- "$flake" | jq -r .url)
 fi
 
-if [[ ! -e $NIXOS_CONFIG && -z $system && -z $flake ]]; then
+if [[ ! -e $NIXOS_CONFIG && -z $system && -z $flake && -n $buildingAttribute ]]; then
     echo "configuration file $NIXOS_CONFIG doesn't exist"
     exit 1
 fi
 
+if [[ ! -z $buildingAttribute && -e $buildFile && -z $system ]]; then
+    echo "configuration file $buildFile doesn't exist"
+    exit 1
+fi
+
 # A place to drop temporary stuff.
 tmpdir="$(mktemp -d -p "$mountPoint")"
 trap 'rm -rf $tmpdir' EXIT
@@ -163,11 +202,20 @@ fi
 # Build the system configuration in the target filesystem.
 if [[ -z $system ]]; then
     outLink="$tmpdir/system"
-    if [[ -z $flake ]]; then
+    if [[ -z $flake && -n $buildingAttribute ]]; then
         echo "building the configuration in $NIXOS_CONFIG..."
         nix-build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \
             --extra-substituters "$sub" \
             '<nixpkgs/nixos>' -A system -I "nixos-config=$NIXOS_CONFIG" "${verbosity[@]}"
+    elif [[ -z $buildingAttribute ]]; then
+        if [[ -n $attr ]]; then
+            echo "building the configuration in $buildFile and attribute $attr..."
+        else
+            echo "building the configuration in $buildFile..."
+        fi
+        nix-build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \
+            --extra-substituters "$sub" \
+            "$buildFile" -A "${attr:+$attr.}config.system.build.toplevel" "${verbosity[@]}"
     else
         echo "building the flake in $flake..."
         nix "${flakeFlags[@]}" build "$flake#$flakeAttr.config.system.build.toplevel" \
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index f26917c0bad59..1bdaf713ab5dc 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -180,7 +180,7 @@ in
       #dnsmasq = 141;# dynamically allocated as of 2021-09-03
       #uhub = 142; # unused
       yandexdisk = 143;
-      mxisd = 144; # was once collectd
+      # mxisd = 144; # removed 2024-07-10
       #consul = 145;# dynamically allocated as of 2021-09-03
       #mailpile = 146; # removed 2022-01-12
       redmine = 147;
@@ -327,7 +327,7 @@ in
       hdfs = 295;
       mapred = 296;
       hadoop = 297;
-      hydron = 298;
+      #hydron = 298; # removed 2024-08-03
       cfssl = 299;
       cassandra = 300;
       qemu-libvirtd = 301;
@@ -503,7 +503,7 @@ in
       #dnsmasq = 141; # unused
       uhub = 142;
       #yandexdisk = 143; # unused
-      mxisd = 144; # was once collectd
+      # mxisd = 144; # removed 2024-07-10
       #consul = 145; # unused
       #mailpile = 146; # removed 2022-01-12
       redmine = 147;
@@ -637,7 +637,7 @@ in
       hdfs = 295;
       mapred = 296;
       hadoop = 297;
-      hydron = 298;
+      #hydron = 298; # removed 2024-08-03
       cfssl = 299;
       cassandra = 300;
       qemu-libvirtd = 301;
diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix
index 0e9adefff5e1e..4692ed15a9567 100644
--- a/nixos/modules/misc/locate.nix
+++ b/nixos/modules/misc/locate.nix
@@ -297,7 +297,10 @@ in
       description = "Update timer for locate database";
       partOf = [ "update-locatedb.service" ];
       wantedBy = [ "timers.target" ];
-      timerConfig.OnCalendar = cfg.interval;
+      timerConfig = {
+        OnCalendar = cfg.interval;
+        Persistent = true;
+      };
     };
   };
 
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index b4c9faeef29ec..599ea640f67cb 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -59,6 +59,7 @@
   ./hardware/cpu/intel-microcode.nix
   ./hardware/cpu/intel-sgx.nix
   ./hardware/cpu/x86-msr.nix
+  ./hardware/decklink.nix
   ./hardware/device-tree.nix
   ./hardware/digitalbitbox.nix
   ./hardware/flipperzero.nix
@@ -80,6 +81,7 @@
   ./hardware/mcelog.nix
   ./hardware/network/ath-user-regd.nix
   ./hardware/network/b43.nix
+  ./hardware/network/eg25-manager.nix
   ./hardware/network/intel-2200bg.nix
   ./hardware/new-lg4ff.nix
   ./hardware/nitrokey.nix
@@ -180,6 +182,7 @@
   ./programs/dublin-traceroute.nix
   ./programs/ecryptfs.nix
   ./programs/environment.nix
+  ./programs/envision.nix
   ./programs/evince.nix
   ./programs/extra-container.nix
   ./programs/fcast-receiver.nix
@@ -204,16 +207,19 @@
   ./programs/goldwarden.nix
   ./programs/gpaste.nix
   ./programs/gphoto2.nix
+  ./programs/gpu-screen-recorder.nix
   ./programs/haguichi.nix
   ./programs/hamster.nix
   ./programs/htop.nix
   ./programs/iay.nix
   ./programs/iftop.nix
   ./programs/i3lock.nix
+  ./programs/immersed-vr.nix
   ./programs/iotop.nix
   ./programs/java.nix
   ./programs/joycond-cemuhook.nix
   ./programs/k3b.nix
+  ./programs/kde-pim.nix
   ./programs/k40-whisperer.nix
   ./programs/kbdlight.nix
   ./programs/kclock.nix
@@ -224,6 +230,7 @@
   ./programs/less.nix
   ./programs/liboping.nix
   ./programs/light.nix
+  ./programs/localsend.nix
   ./programs/mdevctl.nix
   ./programs/mepo.nix
   ./programs/mininet.nix
@@ -257,6 +264,7 @@
   ./programs/projecteur.nix
   ./programs/proxychains.nix
   ./programs/qdmr.nix
+  ./programs/qgroundcontrol.nix
   ./programs/qt5ct.nix
   ./programs/quark-goldleaf.nix
   ./programs/regreet.nix
@@ -298,6 +306,7 @@
   ./programs/wayland/hyprlock.nix
   ./programs/wayland/hyprland.nix
   ./programs/wayland/labwc.nix
+  ./programs/wayland/miracle-wm.nix
   ./programs/wayland/river.nix
   ./programs/wayland/sway.nix
   ./programs/wayland/waybar.nix
@@ -350,6 +359,7 @@
   ./security/systemd-confinement.nix
   ./security/tpm2.nix
   ./security/wrappers/default.nix
+  ./services/accessibility/speechd.nix
   ./services/admin/docuum.nix
   ./services/admin/meshcentral.nix
   ./services/admin/oxidized.nix
@@ -371,6 +381,7 @@
   ./services/audio/mopidy.nix
   ./services/audio/mpd.nix
   ./services/audio/mpdscribble.nix
+  ./services/audio/music-assistant.nix
   ./services/audio/mympd.nix
   ./services/audio/navidrome.nix
   ./services/audio/networkaudiod.nix
@@ -407,6 +418,7 @@
   ./services/blockchain/ethereum/geth.nix
   ./services/blockchain/ethereum/lighthouse.nix
   ./services/cluster/corosync/default.nix
+  ./services/cluster/druid/default.nix
   ./services/cluster/hadoop/default.nix
   ./services/cluster/k3s/default.nix
   ./services/cluster/kubernetes/addon-manager.nix
@@ -531,6 +543,7 @@
   ./services/display-managers/default.nix
   ./services/display-managers/greetd.nix
   ./services/display-managers/sddm.nix
+  ./services/display-managers/ly.nix
   ./services/editors/emacs.nix
   ./services/editors/haste.nix
   ./services/editors/infinoted.nix
@@ -561,6 +574,7 @@
   ./services/hardware/bolt.nix
   ./services/hardware/brltty.nix
   ./services/hardware/ddccontrol.nix
+  ./services/hardware/display.nix
   ./services/hardware/fancontrol.nix
   ./services/hardware/freefall.nix
   ./services/hardware/fwupd.nix
@@ -660,6 +674,7 @@
   ./services/mail/postfixadmin.nix
   ./services/mail/postgrey.nix
   ./services/mail/postsrsd.nix
+  ./services/mail/protonmail-bridge.nix
   ./services/mail/public-inbox.nix
   ./services/mail/roundcube.nix
   ./services/mail/rspamd.nix
@@ -701,6 +716,7 @@
   ./services/misc/beanstalkd.nix
   ./services/misc/bees.nix
   ./services/misc/bepasty.nix
+  ./services/misc/blenderfarm.nix
   ./services/misc/calibre-server.nix
   ./services/misc/canto-daemon.nix
   ./services/misc/cfdyndns.nix
@@ -724,6 +740,7 @@
   ./services/misc/etesync-dav.nix
   ./services/misc/evdevremapkeys.nix
   ./services/misc/felix.nix
+  ./services/misc/flaresolverr.nix
   ./services/misc/forgejo.nix
   ./services/misc/freeswitch.nix
   ./services/misc/fstrim.nix
@@ -735,6 +752,7 @@
   ./services/misc/gitweb.nix
   ./services/misc/gogs.nix
   ./services/misc/gollum.nix
+  ./services/misc/gotenberg.nix
   ./services/misc/gpsd.nix
   ./services/misc/graphical-desktop.nix
   ./services/misc/greenclip.nix
@@ -752,7 +770,6 @@
   ./services/misc/klipper.nix
   ./services/misc/languagetool.nix
   ./services/misc/leaps.nix
-  ./services/misc/libreddit.nix
   ./services/misc/lidarr.nix
   ./services/misc/lifecycled.nix
   ./services/misc/llama-cpp.nix
@@ -796,7 +813,9 @@
   ./services/misc/pufferpanel.nix
   ./services/misc/pykms.nix
   ./services/misc/radarr.nix
+  ./services/misc/radicle.nix
   ./services/misc/readarr.nix
+  ./services/misc/redlib.nix
   ./services/misc/redmine.nix
   ./services/misc/renovate.nix
   ./services/misc/ripple-data-api.nix
@@ -836,7 +855,6 @@
   ./services/misc/wastebin.nix
   ./services/misc/weechat.nix
   ./services/misc/workout-tracker.nix
-  ./services/misc/xmr-stak.nix
   ./services/misc/xmrig.nix
   ./services/misc/zoneminder.nix
   ./services/misc/zookeeper.nix
@@ -971,6 +989,7 @@
   ./services/networking/clatd.nix
   ./services/networking/cloudflare-dyndns.nix
   ./services/networking/cloudflared.nix
+  ./services/networking/cloudflare-warp.nix
   ./services/networking/cntlm.nix
   ./services/networking/connman.nix
   ./services/networking/consul.nix
@@ -983,6 +1002,7 @@
   ./services/networking/dante.nix
   ./services/networking/deconz.nix
   ./services/networking/ddclient.nix
+  ./services/networking/ddns-updater.nix
   ./services/networking/dhcpcd.nix
   ./services/networking/dnscache.nix
   ./services/networking/dnscrypt-proxy2.nix
@@ -1028,6 +1048,7 @@
   ./services/networking/harmonia.nix
   ./services/networking/haproxy.nix
   ./services/networking/headscale.nix
+  ./services/networking/hickory-dns.nix
   ./services/networking/hostapd.nix
   ./services/networking/htpdate.nix
   ./services/networking/https-dns-proxy.nix
@@ -1084,7 +1105,6 @@
   ./services/networking/mullvad-vpn.nix
   ./services/networking/multipath.nix
   ./services/networking/murmur.nix
-  ./services/networking/mxisd.nix
   ./services/networking/mycelium.nix
   ./services/networking/namecoind.nix
   ./services/networking/nar-serve.nix
@@ -1125,6 +1145,7 @@
   ./services/networking/oink.nix
   ./services/networking/onedrive.nix
   ./services/networking/openconnect.nix
+  ./services/networking/opengfw.nix
   ./services/networking/openvpn.nix
   ./services/networking/ostinato.nix
   ./services/networking/owamp.nix
@@ -1147,7 +1168,9 @@
   ./services/networking/r53-ddns.nix
   ./services/networking/radicale.nix
   ./services/networking/radvd.nix
+  ./services/networking/rathole.nix
   ./services/networking/rdnssd.nix
+  ./services/networking/realm.nix
   ./services/networking/redsocks.nix
   ./services/networking/resilio.nix
   ./services/networking/robustirc-bridge.nix
@@ -1213,7 +1236,6 @@
   ./services/networking/tox-node.nix
   ./services/networking/toxvpn.nix
   ./services/networking/trickster.nix
-  ./services/networking/trust-dns.nix
   ./services/networking/tvheadend.nix
   ./services/networking/twingate.nix
   ./services/networking/ucarp.nix
@@ -1228,6 +1250,7 @@
   ./services/networking/websockify.nix
   ./services/networking/wg-access-server.nix
   ./services/networking/wg-netmanager.nix
+  ./services/networking/wvdial.nix
   ./services/networking/webhook.nix
   ./services/networking/wg-quick.nix
   ./services/networking/wgautomesh.nix
@@ -1244,6 +1267,7 @@
   ./services/networking/zerobin.nix
   ./services/networking/zeronet.nix
   ./services/networking/zerotierone.nix
+  ./services/networking/zeronsd.nix
   ./services/networking/znc/default.nix
   ./services/printing/cupsd.nix
   ./services/printing/ipp-usb.nix
@@ -1260,6 +1284,7 @@
   ./services/search/qdrant.nix
   ./services/search/quickwit.nix
   ./services/search/sonic-server.nix
+  ./services/search/tika.nix
   ./services/search/typesense.nix
   ./services/security/aesmd.nix
   ./services/security/authelia.nix
@@ -1337,7 +1362,6 @@
   ./services/video/frigate.nix
   ./services/video/mirakurun.nix
   ./services/video/photonvision.nix
-  ./services/video/replay-sorcery.nix
   ./services/video/mediamtx.nix
   ./services/video/unifi-video.nix
   ./services/video/v4l2-relayd.nix
@@ -1364,11 +1388,13 @@
   ./services/web-apps/convos.nix
   ./services/web-apps/crabfit.nix
   ./services/web-apps/davis.nix
+  ./services/web-apps/cryptpad.nix
   ./services/web-apps/dex.nix
   ./services/web-apps/discourse.nix
   ./services/web-apps/documize.nix
   ./services/web-apps/dokuwiki.nix
   ./services/web-apps/dolibarr.nix
+  ./services/web-apps/eintopf.nix
   ./services/web-apps/engelsystem.nix
   ./services/web-apps/ethercalc.nix
   ./services/web-apps/filesender.nix
@@ -1378,10 +1404,12 @@
   ./services/web-apps/freshrss.nix
   ./services/web-apps/galene.nix
   ./services/web-apps/gerrit.nix
+  ./services/web-apps/glance.nix
   ./services/web-apps/gotify-server.nix
   ./services/web-apps/gotosocial.nix
   ./services/web-apps/grocy.nix
   ./services/web-apps/pixelfed.nix
+  ./services/web-apps/goatcounter.nix
   ./services/web-apps/guacamole-client.nix
   ./services/web-apps/guacamole-server.nix
   ./services/web-apps/healthchecks.nix
@@ -1390,6 +1418,7 @@
   ./services/web-apps/honk.nix
   ./services/web-apps/icingaweb2/icingaweb2.nix
   ./services/web-apps/icingaweb2/module-monitoring.nix
+  ./services/web-apps/ifm.nix
   ./services/web-apps/invidious.nix
   ./services/web-apps/invoiceplane.nix
   ./services/web-apps/isso.nix
@@ -1411,6 +1440,7 @@
   ./services/web-apps/meme-bingo-web.nix
   ./services/web-apps/microbin.nix
   ./services/web-apps/miniflux.nix
+  ./services/web-apps/misskey.nix
   ./services/web-apps/monica.nix
   ./services/web-apps/moodle.nix
   ./services/web-apps/movim.nix
@@ -1440,6 +1470,7 @@
   ./services/web-apps/pretix.nix
   ./services/web-apps/prosody-filer.nix
   ./services/web-apps/rimgo.nix
+  ./services/web-apps/screego.nix
   ./services/web-apps/sftpgo.nix
   ./services/web-apps/suwayomi-server.nix
   ./services/web-apps/rss-bridge.nix
@@ -1450,9 +1481,11 @@
   ./services/web-apps/slskd.nix
   ./services/web-apps/snipe-it.nix
   ./services/web-apps/sogo.nix
+  ./services/web-apps/stirling-pdf.nix
   ./services/web-apps/trilium.nix
   ./services/web-apps/tt-rss.nix
   ./services/web-apps/vikunja.nix
+  ./services/web-apps/weblate.nix
   ./services/web-apps/whitebophir.nix
   ./services/web-apps/wiki-js.nix
   ./services/web-apps/windmill.nix
@@ -1470,7 +1503,6 @@
   ./services/web-servers/fcgiwrap.nix
   ./services/web-servers/garage.nix
   ./services/web-servers/hitch/default.nix
-  ./services/web-servers/hydron.nix
   ./services/web-servers/jboss/default.nix
   ./services/web-servers/keter
   ./services/web-servers/lighttpd/cgit.nix
@@ -1649,6 +1681,7 @@
   ./virtualisation/ecs-agent.nix
   ./virtualisation/hyperv-guest.nix
   ./virtualisation/incus.nix
+  ./virtualisation/incus-agent.nix
   ./virtualisation/kvmgt.nix
   ./virtualisation/libvirtd.nix
   ./virtualisation/lxc.nix
diff --git a/nixos/modules/profiles/docker-container.nix b/nixos/modules/profiles/docker-container.nix
index 5365e49711dce..199156edfdc60 100644
--- a/nixos/modules/profiles/docker-container.nix
+++ b/nixos/modules/profiles/docker-container.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, pkgs, ... }:
 
 let
   inherit (pkgs) writeScript;
diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix
index 0b10c04141470..7e5aec053df92 100644
--- a/nixos/modules/profiles/installation-device.nix
+++ b/nixos/modules/profiles/installation-device.nix
@@ -125,6 +125,16 @@ with lib;
     '';
 
     # allow nix-copy to live system
-    nix.settings.trusted-users = [ "root" "nixos" ];
+    nix.settings.trusted-users = [ "nixos" ];
+
+    # Install less voices for speechd to save some space
+    services.speechd.package = pkgs.speechd.override {
+      mbrola = pkgs.mbrola.override {
+        mbrola-voices = pkgs.mbrola-voices.override {
+          # only ship with one voice per language
+          languages = [ "*1" ];
+        };
+      };
+    };
   };
 }
diff --git a/nixos/modules/profiles/macos-builder.nix b/nixos/modules/profiles/macos-builder.nix
index 786e26cf98f7f..bf8414e1e1087 100644
--- a/nixos/modules/profiles/macos-builder.nix
+++ b/nixos/modules/profiles/macos-builder.nix
@@ -123,7 +123,7 @@ in
 
       max-free = cfg.max-free;
 
-      trusted-users = [ "root" user ];
+      trusted-users = [ user ];
     };
 
     services = {
diff --git a/nixos/modules/profiles/perlless.nix b/nixos/modules/profiles/perlless.nix
index 010e4f8f2a28e..6257589ef9eba 100644
--- a/nixos/modules/profiles/perlless.nix
+++ b/nixos/modules/profiles/perlless.nix
@@ -6,10 +6,12 @@
 
 {
 
-  # Disable switching to a new configuration. This is not a necessary
-  # limitation of a perlless system but just a current one. In the future,
-  # perlless switching might be possible.
-  system.switch.enable = lib.mkDefault false;
+  # switch-to-configuration-ng reimplements switch-to-configuration, but
+  # without perl.
+  system.switch = lib.mkDefault {
+    enable = false;
+    enableNg = true;
+  };
 
   # Remove perl from activation
   boot.initrd.systemd.enable = lib.mkDefault true;
@@ -21,6 +23,7 @@
   programs.less.lessopen = lib.mkDefault null;
   programs.command-not-found.enable = lib.mkDefault false;
   boot.enableContainers = lib.mkDefault false;
+  boot.loader.grub.enable = lib.mkDefault false;
   environment.defaultPackages = lib.mkDefault [ ];
   documentation.info.enable = lib.mkDefault false;
 
diff --git a/nixos/modules/profiles/qemu-guest.nix b/nixos/modules/profiles/qemu-guest.nix
index 8b3df97ae0db9..3dbbbbea182e1 100644
--- a/nixos/modules/profiles/qemu-guest.nix
+++ b/nixos/modules/profiles/qemu-guest.nix
@@ -1,17 +1,9 @@
 # Common configuration for virtual machines running under QEMU (using
 # virtio).
 
-{ config, lib, ... }:
+{ ... }:
 
 {
   boot.initrd.availableKernelModules = [ "virtio_net" "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" "9p" "9pnet_virtio" ];
-  boot.initrd.kernelModules = [ "virtio_balloon" "virtio_console" "virtio_rng" ];
-
-  boot.initrd.postDeviceCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
-    ''
-      # Set the system time from the hardware clock to work around a
-      # bug in qemu-kvm > 1.5.2 (where the VM clock is initialised
-      # to the *boot time* of the host).
-      hwclock -s
-    '';
+  boot.initrd.kernelModules = [ "virtio_balloon" "virtio_console" "virtio_rng" "virtio_gpu" ];
 }
diff --git a/nixos/modules/programs/appimage.nix b/nixos/modules/programs/appimage.nix
index 0011c2ff578dc..c0379557c97ab 100644
--- a/nixos/modules/programs/appimage.nix
+++ b/nixos/modules/programs/appimage.nix
@@ -1,4 +1,9 @@
-{ lib, config, pkgs, ... }:
+{
+  lib,
+  config,
+  pkgs,
+  ...
+}:
 
 let
   cfg = config.programs.appimage;
@@ -18,16 +23,31 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    boot.binfmt.registrations.appimage = lib.mkIf cfg.binfmt {
-      wrapInterpreterInShell = false;
-      interpreter = lib.getExe cfg.package;
-      recognitionType = "magic";
-      offset = 0;
-      mask = ''\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff'';
-      magicOrExtension = ''\x7fELF....AI\x02'';
-    };
+    boot.binfmt.registrations = lib.mkIf cfg.binfmt (
+      let
+        appimage_common = {
+          wrapInterpreterInShell = false;
+          interpreter = lib.getExe cfg.package;
+          recognitionType = "magic";
+          offset = 0;
+          mask = ''\xff\xff\xff\xff\x00\x00\x00\x00\xff\xff\xff'';
+        };
+      in
+      {
+        appimage_type_1 = appimage_common // {
+          magicOrExtension = ''\x7fELF....AI\x01'';
+        };
+        appimage_type_2 = appimage_common // {
+          magicOrExtension = ''\x7fELF....AI\x02'';
+        };
+      }
+    );
     environment.systemPackages = [ cfg.package ];
   };
 
-  meta.maintainers = with lib.maintainers; [ jopejoe1 atemu ];
+  meta.maintainers = with lib.maintainers; [
+    jopejoe1
+    atemu
+    aleksana
+  ];
 }
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 4c06f0aad9f81..1ffa7fe561093 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -10,7 +10,7 @@ let
   cfg = config.programs.bash;
 
   bashAliases = builtins.concatStringsSep "\n" (
-    lib.mapAttrsFlatten (k: v: "alias -- ${k}=${lib.escapeShellArg v}")
+    lib.mapAttrsToList (k: v: "alias -- ${k}=${lib.escapeShellArg v}")
       (lib.filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
diff --git a/nixos/modules/programs/benchexec.nix b/nixos/modules/programs/benchexec.nix
index 652670c117ea3..08ee67ca90ae5 100644
--- a/nixos/modules/programs/benchexec.nix
+++ b/nixos/modules/programs/benchexec.nix
@@ -59,14 +59,7 @@ in
         '';
       })
       (builtins.filter builtins.isInt cfg.users)
-    ) ++ [
-      {
-        assertion = config.systemd.enableUnifiedCgroupHierarchy == true;
-        message = ''
-          The BenchExec module `${opt.enable}` only supports control groups 2 (`${options.systemd.enableUnifiedCgroupHierarchy} = true`).
-        '';
-      }
-    ];
+    );
 
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/programs/chromium.nix b/nixos/modules/programs/chromium.nix
index 4d248dbe0945f..65a334b46f0aa 100644
--- a/nixos/modules/programs/chromium.nix
+++ b/nixos/modules/programs/chromium.nix
@@ -17,7 +17,9 @@ in
 
   options = {
     programs.chromium = {
-      enable = lib.mkEnableOption "{command}`chromium` policies";
+      enable = lib.mkEnableOption "the {command}`chromium` web browser";
+
+      package = lib.mkPackageOption pkgs "chromium" { };
 
       enablePlasmaBrowserIntegration = lib.mkEnableOption "Native Messaging Host for Plasma Browser Integration";
 
@@ -119,8 +121,9 @@ in
 
   ###### implementation
 
-  config = {
-    environment.etc = lib.mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    environment.etc = {
       # for chromium
       "chromium/native-messaging-hosts/org.kde.plasma.browser_integration.json" = lib.mkIf cfg.enablePlasmaBrowserIntegration
         { source = "${cfg.plasmaBrowserIntegrationPackage}/etc/chromium/native-messaging-hosts/org.kde.plasma.browser_integration.json"; };
diff --git a/nixos/modules/programs/direnv.nix b/nixos/modules/programs/direnv.nix
index 6061de58eb8e2..f127e959ef01b 100644
--- a/nixos/modules/programs/direnv.nix
+++ b/nixos/modules/programs/direnv.nix
@@ -3,9 +3,18 @@
   config,
   pkgs,
   ...
-}: let
+}:
+let
   cfg = config.programs.direnv;
-in {
+  enabledOption =
+    x:
+    lib.mkEnableOption x
+    // {
+      default = true;
+      example = false;
+    };
+in
+{
   options.programs.direnv = {
 
     enable = lib.mkEnableOption ''
@@ -14,7 +23,17 @@ in {
       integration. Note that you need to logout and login for this change to apply
     '';
 
-    package = lib.mkPackageOption pkgs "direnv" {};
+    package = lib.mkPackageOption pkgs "direnv" { };
+
+    enableBashIntegration = enabledOption ''
+      Bash integration
+    '';
+    enableZshIntegration = enabledOption ''
+      Zsh integration
+    '';
+    enableFishIntegration = enabledOption ''
+      Fish integration
+    '';
 
     direnvrcExtra = lib.mkOption {
       type = lib.types.lines;
@@ -32,22 +51,14 @@ in {
       the hiding of direnv logging
     '';
 
-    loadInNixShell =
-      lib.mkEnableOption ''
-        loading direnv in `nix-shell` `nix shell` or `nix develop`
-      ''
-      // {
-        default = true;
-      };
+    loadInNixShell = enabledOption ''
+      loading direnv in `nix-shell` `nix shell` or `nix develop`
+    '';
 
     nix-direnv = {
-      enable =
-        (lib.mkEnableOption ''
-          a faster, persistent implementation of use_nix and use_flake, to replace the built-in one
-        '')
-        // {
-          default = true;
-        };
+      enable = enabledOption ''
+        a faster, persistent implementation of use_nix and use_flake, to replace the builtin one
+      '';
 
       package = lib.mkOption {
         default = pkgs.nix-direnv.override { nix = config.nix.package; };
@@ -60,14 +71,10 @@ in {
     };
   };
 
-  imports = [
-    (lib.mkRemovedOptionModule ["programs" "direnv" "persistDerivations"] "persistDerivations was removed as it is no longer necessary")
-  ];
-
   config = lib.mkIf cfg.enable {
 
     programs = {
-      zsh.interactiveShellInit = ''
+      zsh.interactiveShellInit = lib.mkIf cfg.enableZshIntegration ''
         if ${lib.boolToString cfg.loadInNixShell} || printenv PATH | grep -vqc '/nix/store'; then
          eval "$(${lib.getExe cfg.package} hook zsh)"
         fi
@@ -75,13 +82,13 @@ in {
 
       #$NIX_GCROOT for "nix develop" https://github.com/NixOS/nix/blob/6db66ebfc55769edd0c6bc70fcbd76246d4d26e0/src/nix/develop.cc#L530
       #$IN_NIX_SHELL for "nix-shell"
-      bash.interactiveShellInit = ''
+      bash.interactiveShellInit = lib.mkIf cfg.enableBashIntegration ''
         if ${lib.boolToString cfg.loadInNixShell} || [ -z "$IN_NIX_SHELL$NIX_GCROOT$(printenv PATH | grep '/nix/store')" ] ; then
          eval "$(${lib.getExe cfg.package} hook bash)"
         fi
       '';
 
-      fish.interactiveShellInit = ''
+      fish.interactiveShellInit = lib.mkIf cfg.enableFishIntegration ''
         if ${lib.boolToString cfg.loadInNixShell};
         or printenv PATH | grep -vqc '/nix/store';
          ${lib.getExe cfg.package} hook fish | source
@@ -90,18 +97,17 @@ in {
     };
 
     environment = {
-      systemPackages =
-        if cfg.loadInNixShell then [cfg.package]
-        else [
-          #direnv has a fish library which sources direnv for some reason
-          (cfg.package.overrideAttrs (old: {
-            installPhase =
-              (old.installPhase or "")
-              + ''
-                rm -rf $out/share/fish
-              '';
-          }))
-        ];
+      systemPackages = [
+        # direnv has a fish library which automatically sources direnv for some reason
+        # I don't see any harm in doing this if we're sourcing it with fish.interactiveShellInit
+        (pkgs.symlinkJoin {
+          inherit (cfg.package) name;
+          paths = [ cfg.package ];
+          postBuild = ''
+            rm -rf $out/share/fish
+          '';
+        })
+      ];
 
       variables = {
         DIRENV_CONFIG = "/etc/direnv";
@@ -141,4 +147,5 @@ in {
       };
     };
   };
+  meta.maintainers = with lib.maintainers; [ gerg-l ];
 }
diff --git a/nixos/modules/programs/dmrconfig.nix b/nixos/modules/programs/dmrconfig.nix
index 0078ca19f41a1..e2136765093aa 100644
--- a/nixos/modules/programs/dmrconfig.nix
+++ b/nixos/modules/programs/dmrconfig.nix
@@ -4,7 +4,7 @@ let
   cfg = config.programs.dmrconfig;
 
 in {
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 
   ###### interface
   options = {
diff --git a/nixos/modules/programs/envision.nix b/nixos/modules/programs/envision.nix
new file mode 100644
index 0000000000000..56acd83d7daf8
--- /dev/null
+++ b/nixos/modules/programs/envision.nix
@@ -0,0 +1,51 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.programs.envision;
+in
+{
+
+  options = {
+    programs.envision = {
+      enable = lib.mkEnableOption "envision";
+
+      package = lib.mkPackageOption pkgs "envision" {};
+
+      openFirewall = lib.mkEnableOption "the default ports in the firewall for the WiVRn server" // {
+        default = true;
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.avahi = {
+      enable = true;
+      publish = {
+        enable = true;
+        userServices = true;
+      };
+    };
+
+    services.udev = {
+      enable = true;
+      packages = with pkgs; [
+        android-udev-rules
+        xr-hardware
+      ];
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    networking.firewall = lib.mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 9757 ];
+      allowedUDPPorts = [ 9757 ];
+    };
+  };
+
+  meta.maintainers = pkgs.envision.meta.maintainers;
+}
diff --git a/nixos/modules/programs/firefox.nix b/nixos/modules/programs/firefox.nix
index 7e0dec57d2dac..7521708fa94f2 100644
--- a/nixos/modules/programs/firefox.nix
+++ b/nixos/modules/programs/firefox.nix
@@ -162,6 +162,7 @@ in
         "ff"
         "fi"
         "fr"
+        "fur"
         "fy-NL"
         "ga-IE"
         "gd"
@@ -204,9 +205,12 @@ in
         "rm"
         "ro"
         "ru"
+        "sat"
+        "sc"
         "sco"
         "si"
         "sk"
+        "skr"
         "sl"
         "son"
         "sq"
@@ -215,6 +219,7 @@ in
         "szl"
         "ta"
         "te"
+        "tg"
         "th"
         "tl"
         "tr"
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index 5a6fdb9b5ec5a..ef31a404bcb80 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -7,12 +7,12 @@ let
   cfg = config.programs.fish;
 
   fishAbbrs = lib.concatStringsSep "\n" (
-    lib.mapAttrsFlatten (k: v: "abbr -ag ${k} ${lib.escapeShellArg v}")
+    lib.mapAttrsToList (k: v: "abbr -ag ${k} ${lib.escapeShellArg v}")
       cfg.shellAbbrs
   );
 
   fishAliases = lib.concatStringsSep "\n" (
-    lib.mapAttrsFlatten (k: v: "alias ${k} ${lib.escapeShellArg v}")
+    lib.mapAttrsToList (k: v: "alias ${k} ${lib.escapeShellArg v}")
       (lib.filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
diff --git a/nixos/modules/programs/gpu-screen-recorder.nix b/nixos/modules/programs/gpu-screen-recorder.nix
new file mode 100644
index 0000000000000..39d0e25452415
--- /dev/null
+++ b/nixos/modules/programs/gpu-screen-recorder.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.gpu-screen-recorder;
+  package = cfg.package.override {
+    inherit (config.security) wrapperDir;
+  };
+in {
+  options = {
+    programs.gpu-screen-recorder = {
+      package = lib.mkPackageOption pkgs "gpu-screen-recorder" {};
+
+      enable = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = ''
+          Whether to install gpu-screen-recorder and generate setcap
+          wrappers for promptless recording.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    security.wrappers."gsr-kms-server" = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_sys_admin+ep";
+      source = "${package}/bin/gsr-kms-server";
+    };
+    security.wrappers."gpu-screen-recorder" = {
+      owner = "root";
+      group = "root";
+      capabilities = "cap_sys_nice+ep";
+      source = "${package}/bin/gpu-screen-recorder";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ timschumi ];
+}
diff --git a/nixos/modules/programs/immersed-vr.nix b/nixos/modules/programs/immersed-vr.nix
new file mode 100644
index 0000000000000..57edb3cbaea06
--- /dev/null
+++ b/nixos/modules/programs/immersed-vr.nix
@@ -0,0 +1,34 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.programs.immersed-vr;
+in
+{
+
+  options = {
+    programs.immersed-vr = {
+      enable = lib.mkEnableOption "immersed-vr";
+
+      package = lib.mkPackageOption pkgs "immersed-vr" {};
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    boot = {
+      kernelModules = [ "v4l2loopback" "snd-aloop" ];
+      extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ];
+      extraModprobeConfig = ''
+        options v4l2loopback exclusive_caps=1 card_label="v4l2loopback Virtual Camera"
+      '';
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+
+  meta.maintainers = pkgs.immersed-vr.meta.maintainers;
+}
diff --git a/nixos/modules/programs/kde-pim.nix b/nixos/modules/programs/kde-pim.nix
new file mode 100644
index 0000000000000..af3c45b690c20
--- /dev/null
+++ b/nixos/modules/programs/kde-pim.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.programs.kde-pim;
+in
+{
+  options.programs.kde-pim = {
+    enable = lib.mkEnableOption "KDE PIM base packages";
+    kmail = lib.mkEnableOption "KMail";
+    kontact = lib.mkEnableOption "Kontact";
+    merkuro = lib.mkEnableOption "Merkuro";
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = with pkgs.kdePackages; [
+      # core packages
+      akonadi
+      kdepim-runtime
+    ] ++ lib.optionals cfg.kmail [
+      akonadiconsole
+      akonadi-search
+      kmail
+      kmail-account-wizard
+    ] ++ lib.optionals cfg.kontact [
+      kontact
+    ] ++ lib.optionals cfg.merkuro [
+      merkuro
+    ];
+  };
+}
diff --git a/nixos/modules/programs/localsend.nix b/nixos/modules/programs/localsend.nix
new file mode 100644
index 0000000000000..47f54246a40f7
--- /dev/null
+++ b/nixos/modules/programs/localsend.nix
@@ -0,0 +1,26 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.programs.localsend;
+  firewallPort = 53317;
+in
+{
+  options.programs.localsend = {
+    enable = lib.mkEnableOption "localsend, an open source cross-platform alternative to AirDrop";
+
+    openFirewall = lib.mkEnableOption "opening the firewall port ${toString firewallPort} for receiving files" // {
+      default = true;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.localsend ];
+    networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall [ firewallPort ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ pandapip1 ];
+}
diff --git a/nixos/modules/programs/miriway.nix b/nixos/modules/programs/miriway.nix
index 418bb3dc4f2dd..b3d103f8a89d8 100644
--- a/nixos/modules/programs/miriway.nix
+++ b/nixos/modules/programs/miriway.nix
@@ -72,6 +72,8 @@ in {
 
     # To make the Miriway session available if a display manager like SDDM is enabled:
     services.displayManager.sessionPackages = [ pkgs.miriway ];
+
+    xdg.icons.enable = true;
   };
 
   meta.maintainers = with lib.maintainers; [ OPNA2608 ];
diff --git a/nixos/modules/programs/nano.nix b/nixos/modules/programs/nano.nix
index 10fa2a0dfbcdf..1ae350ea66b28 100644
--- a/nixos/modules/programs/nano.nix
+++ b/nixos/modules/programs/nano.nix
@@ -43,6 +43,7 @@ in
         include "${cfg.package}/share/nano/extra/*.nanorc"
       '') + cfg.nanorc;
       systemPackages = [ cfg.package ];
+      pathsToLink = [ "/share/nano" ];
     };
   };
 }
diff --git a/nixos/modules/programs/neovim.nix b/nixos/modules/programs/neovim.nix
index 8fe6a664b675a..142cca9f322a3 100644
--- a/nixos/modules/programs/neovim.nix
+++ b/nixos/modules/programs/neovim.nix
@@ -150,6 +150,10 @@ in
       cfg.finalPackage
     ];
     environment.variables.EDITOR = lib.mkIf cfg.defaultEditor (lib.mkOverride 900 "nvim");
+    # On most NixOS configurations /share is already included, so it includes
+    # this directory as well. But  This makes sure that /share/nvim/site paths
+    # from other packages will be used by neovim.
+    environment.pathsToLink = [ "/share/nvim" ];
 
     environment.etc = builtins.listToAttrs (builtins.attrValues (builtins.mapAttrs
       (name: value: {
diff --git a/nixos/modules/programs/nix-required-mounts.nix b/nixos/modules/programs/nix-required-mounts.nix
index 5d25958a7698d..5064d6aaf575d 100644
--- a/nixos/modules/programs/nix-required-mounts.nix
+++ b/nixos/modules/programs/nix-required-mounts.nix
@@ -47,7 +47,7 @@ let
     );
 
   driverPaths = [
-    pkgs.addOpenGLRunpath.driverLink
+    pkgs.addDriverRunpath.driverLink
 
     # mesa:
     config.hardware.opengl.package
@@ -84,7 +84,7 @@ in
           {
             opengl.paths = config.hardware.opengl.extraPackages ++ [
               config.hardware.opengl.package
-              pkgs.addOpenGLRunpath.driverLink
+              pkgs.addDriverRunpath.driverLink
               "/dev/dri"
             ];
           }
diff --git a/nixos/modules/programs/qgroundcontrol.nix b/nixos/modules/programs/qgroundcontrol.nix
new file mode 100644
index 0000000000000..4534d79f25dd8
--- /dev/null
+++ b/nixos/modules/programs/qgroundcontrol.nix
@@ -0,0 +1,53 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.programs.qgroundcontrol;
+in
+{
+
+  options = {
+    programs.qgroundcontrol = {
+      enable = lib.mkEnableOption "qgroundcontrol";
+
+      package = lib.mkPackageOption pkgs "qgroundcontrol" {};
+
+      blacklistModemManagerFromTTYUSB = lib.mkOption {
+        type = lib.types.bool;
+        default = true;
+        description = ''
+          Disallow ModemManager from interfering with serial connections that QGroundControl might use.
+
+          Note that if you use a modem that's connected via USB, you might want to disable this option.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    # ModemManager is known to interfere with serial connections;
+    # QGC recommends disabling it, but we don't want to if we can avoid it
+    # Instead, we blacklist tty devices using udev rules, which is a more targeted approach
+    services.udev = lib.mkIf cfg.blacklistModemManagerFromTTYUSB {
+      enable = true;
+      extraRules = ''
+        # nixos/qgroundcontrol: Blacklist ttyUSB devices from ModemManager
+        SUBSYSTEM=="tty", KERNEL=="ttyUSB*", ENV{ID_MM_DEVICE_IGNORE}="1"
+      '';
+    };
+
+    # Security wrapper
+    security.wrappers.qgroundcontrol = {
+      source = lib.getExe cfg.package;
+      owner = "root"; # Sensible default; not setuid so this is not a security risk
+      group = "tty";
+      setgid = true;
+    };
+  };
+
+  meta.maintainers = pkgs.qgroundcontrol.meta.maintainers;
+}
diff --git a/nixos/modules/programs/regreet.nix b/nixos/modules/programs/regreet.nix
index 0db1f59e59121..cec3e0bf50462 100644
--- a/nixos/modules/programs/regreet.nix
+++ b/nixos/modules/programs/regreet.nix
@@ -58,9 +58,96 @@ in
         modifiable properties.
       '';
     };
+
+    theme = {
+      package = lib.mkPackageOption pkgs "gnome-themes-extra" { } // {
+        description = ''
+          The package that provides the theme given in the name option.
+        '';
+      };
+
+      name = lib.mkOption {
+        type = lib.types.str;
+        default = "Adwaita";
+        description = ''
+          Name of the theme to use for regreet.
+        '';
+      };
+    };
+
+    iconTheme = {
+      package = lib.mkPackageOption pkgs "adwaita-icon-theme" { } // {
+        description = ''
+          The package that provides the icon theme given in the name option.
+        '';
+      };
+
+      name = lib.mkOption {
+        type = lib.types.str;
+        default = "Adwaita";
+        description = ''
+          Name of the icon theme to use for regreet.
+        '';
+      };
+    };
+
+    font = {
+      package = lib.mkPackageOption pkgs "cantarell-fonts" { } // {
+        description = ''
+          The package that provides the font given in the name option.
+        '';
+      };
+
+      name = lib.mkOption {
+        type = lib.types.str;
+        default = "Cantarell";
+        description = ''
+          Name of the font to use for regreet.
+        '';
+      };
+
+      size = lib.mkOption {
+        type = lib.types.ints.positive;
+        default = 16;
+        description = ''
+          Size of the font to use for regreet.
+        '';
+      };
+    };
+
+    cursorTheme = {
+      package = lib.mkPackageOption pkgs "adwaita-icon-theme" { } // {
+        description = ''
+          The package that provides the cursor theme given in the name option.
+        '';
+      };
+
+      name = lib.mkOption {
+        type = lib.types.str;
+        default = "Adwaita";
+        description = ''
+          Name of the cursor theme to use for regreet.
+        '';
+      };
+    };
   };
 
   config = lib.mkIf cfg.enable {
+    environment.systemPackages = [
+      cfg.theme.package
+      cfg.iconTheme.package
+      cfg.cursorTheme.package
+    ];
+
+    fonts.packages = [ cfg.font.package ];
+
+    programs.regreet.settings = {
+      cursor_theme_name = cfg.cursorTheme.name;
+      font_name = "${cfg.font.name} ${toString cfg.font.size}";
+      icon_theme_name = cfg.iconTheme.name;
+      theme_name = cfg.theme.name;
+    };
+
     services.greetd = {
       enable = lib.mkDefault true;
       settings.default_session.command = lib.mkDefault "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} ${lib.escapeShellArgs cfg.cageArgs} -- ${lib.getExe cfg.package}";
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 0692dd46f7d04..f2ef248d78666 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -293,9 +293,9 @@ in
 
         # Generated options from other settings
         Host *
-        AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
         GlobalKnownHostsFile ${builtins.concatStringsSep " " knownHostsFiles}
 
+        ${lib.optionalString (!config.networking.enableIPv6) "AddressFamily inet"}
         ${lib.optionalString cfg.setXAuthLocation "XAuthLocation ${pkgs.xorg.xauth}/bin/xauth"}
         ${lib.optionalString (cfg.forwardX11 != null) "ForwardX11 ${if cfg.forwardX11 then "yes" else "no"}"}
 
diff --git a/nixos/modules/programs/tmux.nix b/nixos/modules/programs/tmux.nix
index b4b476a801ddf..a157bedc8aae6 100644
--- a/nixos/modules/programs/tmux.nix
+++ b/nixos/modules/programs/tmux.nix
@@ -16,6 +16,7 @@ let
     set  -g default-terminal "${cfg.terminal}"
     set  -g base-index      ${toString cfg.baseIndex}
     setw -g pane-base-index ${toString cfg.baseIndex}
+    set  -g history-limit   ${toString cfg.historyLimit}
 
     ${optionalString cfg.newSession "new-session"}
 
@@ -50,7 +51,6 @@ let
     setw -g aggressive-resize ${boolToStr cfg.aggressiveResize}
     setw -g clock-mode-style  ${if cfg.clock24 then "24" else "12"}
     set  -s escape-time       ${toString cfg.escapeTime}
-    set  -g history-limit     ${toString cfg.historyLimit}
 
     ${cfg.extraConfigBeforePlugins}
 
@@ -230,4 +230,6 @@ in {
   imports = [
     (lib.mkRenamedOptionModule [ "programs" "tmux" "extraTmuxConf" ] [ "programs" "tmux" "extraConfig" ])
   ];
+
+  meta.maintainers = with lib.maintainers; [ hxtmdev ];
 }
diff --git a/nixos/modules/programs/tsm-client.nix b/nixos/modules/programs/tsm-client.nix
index 82fbc9b26e2d3..a010bb6c6d578 100644
--- a/nixos/modules/programs/tsm-client.nix
+++ b/nixos/modules/programs/tsm-client.nix
@@ -4,7 +4,7 @@ let optionsGlobal = options; in
 let
 
   inherit (lib.attrsets) attrNames attrValues mapAttrsToList removeAttrs;
-  inherit (lib.lists) all allUnique concatLists elem isList map;
+  inherit (lib.lists) all allUnique concatLists concatMap elem isList map;
   inherit (lib.modules) mkDefault mkIf;
   inherit (lib.options) mkEnableOption mkOption mkPackageOption;
   inherit (lib.strings) concatLines match optionalString toLower;
@@ -22,7 +22,7 @@ let
   serverOptions = { name, config, ... }: {
     freeformType = attrsOf (either scalarType (listOf scalarType));
     # Client system-options file directives are explained here:
-    # https://www.ibm.com/docs/en/storage-protect/8.1.22?topic=commands-processing-options
+    # https://www.ibm.com/docs/en/storage-protect/8.1.23?topic=commands-processing-options
     options.servername = mkOption {
       type = servernameType;
       default = name;
@@ -231,7 +231,7 @@ let
     # Turn a key-value pair from the server options attrset
     # into zero (value==null), one (scalar value) or
     # more (value is list) configuration stanza lines.
-    if isList value then map (makeDsmSysLines key) value else  # recurse into list
+    if isList value then concatMap (makeDsmSysLines key) value else  # recurse into list
     if value == null then [ ] else  # skip `null` value
     [ ("  ${key}${
       if value == true then "" else  # just output key if value is `true`
diff --git a/nixos/modules/programs/vim.nix b/nixos/modules/programs/vim.nix
index 8232340ddebbf..c84f966eb3a7e 100644
--- a/nixos/modules/programs/vim.nix
+++ b/nixos/modules/programs/vim.nix
@@ -1,25 +1,31 @@
-{ config, lib, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 let
   cfg = config.programs.vim;
-in {
+in
+{
   options.programs.vim = {
-    defaultEditor = lib.mkOption {
-      type = lib.types.bool;
-      default = false;
-      description = ''
-        When enabled, installs vim and configures vim to be the default editor
-        using the EDITOR environment variable.
-      '';
-    };
+    enable = lib.mkEnableOption "Vi IMproved, an advanced text";
 
-    package = lib.mkPackageOption pkgs "vim" {
-      example = "vim-full";
-    };
+    defaultEditor = lib.mkEnableOption "vim as the default editor";
+
+    package = lib.mkPackageOption pkgs "vim" { example = "vim-full"; };
   };
 
-  config = lib.mkIf cfg.defaultEditor {
-    environment.systemPackages = [ cfg.package ];
-    environment.variables = { EDITOR = lib.mkOverride 900 "vim"; };
+  # TODO: convert it into assert after 24.11 release
+  config = lib.mkIf (cfg.enable || cfg.defaultEditor) {
+    warnings = lib.mkIf (cfg.defaultEditor && !cfg.enable) [
+      "programs.vim.defaultEditor will only work if programs.vim.enable is enabled, which will be enforced after the 24.11 release"
+    ];
+    environment = {
+      systemPackages = [ cfg.package ];
+      variables.EDITOR = lib.mkIf cfg.defaultEditor (lib.mkOverride 900 "vim");
+      pathsToLink = [ "/share/vim-plugins" ];
+    };
   };
 }
diff --git a/nixos/modules/programs/wayland/miracle-wm.nix b/nixos/modules/programs/wayland/miracle-wm.nix
new file mode 100644
index 0000000000000..a4c843523dc40
--- /dev/null
+++ b/nixos/modules/programs/wayland/miracle-wm.nix
@@ -0,0 +1,43 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+
+let
+  cfg = config.programs.wayland.miracle-wm;
+in
+{
+  options.programs.wayland.miracle-wm = {
+    enable = lib.mkEnableOption ''
+      miracle-wm, a tiling Mir based Wayland compositor. You can manually launch miracle-wm by
+      executing "exec miracle-wm" on a TTY, or launch it from a display manager.
+      Consult the USERGUIDE.md at <https://github.com/mattkae/miracle-wm> for information on
+      how to use & configure it
+    '';
+  };
+
+  config = lib.mkIf cfg.enable (
+    lib.mkMerge [
+      {
+        environment = {
+          systemPackages = [ pkgs.miracle-wm ];
+        };
+
+        # To make the miracle-wm session available if a display manager like SDDM is enabled:
+        services.displayManager.sessionPackages = [ pkgs.miracle-wm ];
+      }
+
+      (import ./wayland-session.nix {
+        inherit lib pkgs;
+        # Hardcoded path in Mir, not really possible to disable
+        enableXWayland = true;
+        # No portal support yet: https://github.com/mattkae/miracle-wm/issues/164
+        enableWlrPortal = false;
+      })
+    ]
+  );
+
+  meta.maintainers = with lib.maintainers; [ OPNA2608 ];
+}
diff --git a/nixos/modules/programs/wayland/wayfire.nix b/nixos/modules/programs/wayland/wayfire.nix
index 7acc5b2739cbf..381d14fb8e214 100644
--- a/nixos/modules/programs/wayland/wayfire.nix
+++ b/nixos/modules/programs/wayland/wayfire.nix
@@ -1,4 +1,9 @@
-{ config, lib, pkgs, ...}:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 let
   cfg = config.programs.wayfire;
 in
@@ -12,7 +17,10 @@ in
 
     plugins = lib.mkOption {
       type = lib.types.listOf lib.types.package;
-      default = with pkgs.wayfirePlugins; [ wcm wf-shell ];
+      default = with pkgs.wayfirePlugins; [
+        wcm
+        wf-shell
+      ];
       defaultText = lib.literalExpression "with pkgs.wayfirePlugins; [ wcm wf-shell ]";
       example = lib.literalExpression ''
         with pkgs.wayfirePlugins; [
@@ -25,26 +33,41 @@ in
         Additional plugins to use with the wayfire window manager.
       '';
     };
+    xwayland.enable = lib.mkEnableOption "XWayland" // {
+      default = true;
+    };
   };
 
-  config = let
-    finalPackage = pkgs.wayfire-with-plugins.override {
-      wayfire = cfg.package;
-      plugins = cfg.plugins;
-    };
-  in
-  lib.mkIf cfg.enable {
-    environment.systemPackages = [
-      finalPackage
-    ];
+  config =
+    let
+      finalPackage = pkgs.wayfire-with-plugins.override {
+        wayfire = cfg.package;
+        plugins = cfg.plugins;
+      };
+    in
+    lib.mkIf cfg.enable (
+      lib.mkMerge [
+        {
+          environment.systemPackages = [ finalPackage ];
 
-    services.displayManager.sessionPackages = [ finalPackage ];
+          services.displayManager.sessionPackages = [ finalPackage ];
 
-    xdg.portal = {
-      enable = lib.mkDefault true;
-      wlr.enable = lib.mkDefault true;
-      # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050914
-      config.wayfire.default = lib.mkDefault [ "wlr" "gtk" ];
-    };
-  };
+          xdg.icons.enable = true;
+
+          xdg.portal = {
+            enable = lib.mkDefault true;
+            wlr.enable = lib.mkDefault true;
+            # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050914
+            config.wayfire.default = lib.mkDefault [
+              "wlr"
+              "gtk"
+            ];
+          };
+        }
+        (import ./wayland-session.nix {
+          inherit lib pkgs;
+          enableXWayland = cfg.xwayland.enable;
+        })
+      ]
+    );
 }
diff --git a/nixos/modules/programs/wayland/wayland-session.nix b/nixos/modules/programs/wayland/wayland-session.nix
index 09fb2a5f14b2e..18c252794563d 100644
--- a/nixos/modules/programs/wayland/wayland-session.nix
+++ b/nixos/modules/programs/wayland/wayland-session.nix
@@ -19,6 +19,8 @@
     xwayland.enable = lib.mkDefault enableXWayland;
   };
 
+  xdg.icons.enable = true;
+
   xdg.portal.wlr.enable = enableWlrPortal;
 
   # Window manager only sessions (unlike DEs) don't handle XDG
diff --git a/nixos/modules/programs/ydotool.nix b/nixos/modules/programs/ydotool.nix
index 643a5d369f3fc..3377ae4262610 100644
--- a/nixos/modules/programs/ydotool.nix
+++ b/nixos/modules/programs/ydotool.nix
@@ -68,7 +68,6 @@ in
         ProtectKernelTunables = true;
         ProtectProc = "invisible";
         ProtectSystem = "strict";
-        ProtectUser = true;
         RestrictNamespaces = true;
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index 35d2cf4610563..820d8daf81f15 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -10,7 +10,7 @@ let
   opt = options.programs.zsh;
 
   zshAliases = builtins.concatStringsSep "\n" (
-    lib.mapAttrsFlatten (k: v: "alias -- ${k}=${lib.escapeShellArg v}")
+    lib.mapAttrsToList (k: v: "alias -- ${k}=${lib.escapeShellArg v}")
       (lib.filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index d4661a19188c8..df6090e41d7d4 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -1,4 +1,4 @@
-{ lib, pkgs, ... }:
+{ lib, ... }:
 
 let
   inherit (lib)
@@ -67,6 +67,7 @@ in
     (mkRemovedOptionModule [ "services" "fprot" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "frab" ] "The frab module has been removed")
     (mkRemovedOptionModule [ "services" "homeassistant-satellite"] "The `services.homeassistant-satellite` module has been replaced by `services.wyoming-satellite`.")
+    (mkRemovedOptionModule [ "services" "hydron" ] "The `services.hydron` module has been removed as the project has been archived upstream since 2022 and is affected by a severe remote code execution vulnerability.")
     (mkRemovedOptionModule [ "services" "ihatemoney" ] "The ihatemoney module has been removed for lack of downstream maintainer")
     (mkRemovedOptionModule [ "services" "kippo" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "mailpile" ] "The corresponding package was removed from nixpkgs.")
@@ -74,6 +75,7 @@ in
     (mkRemovedOptionModule [ "services" "mathics" ] "The Mathics module has been removed")
     (mkRemovedOptionModule [ "services" "meguca" ] "Use meguca has been removed from nixpkgs")
     (mkRemovedOptionModule [ "services" "mesos" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "services" "mxisd" ] "The mxisd module has been removed as both mxisd and ma1sd got removed.")
     (mkRemovedOptionModule [ "services" "moinmoin" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "mwlib" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "pantheon" "files" ] ''
@@ -84,6 +86,7 @@ in
     '')
     (mkRemovedOptionModule [ "services" "quagga" ] "the corresponding package has been removed from nixpkgs")
     (mkRemovedOptionModule [ "services" "railcar" ] "the corresponding package has been removed from nixpkgs")
+    (mkRemovedOptionModule [ "services" "replay-sorcery" ] "the corresponding package has been removed from nixpkgs as it is unmaintained upstream. Consider using `gpu-screen-recorder` or `obs-studio` instead.")
     (mkRemovedOptionModule [ "services" "seeks" ] "")
     (mkRemovedOptionModule [ "services" "ssmtp" ] ''
       The ssmtp package and the corresponding module have been removed due to
@@ -105,6 +108,7 @@ in
       as the underlying package isn't being maintained. Working alternatives are
       libinput and synaptics.
     '')
+    (mkRemovedOptionModule [ "services" "xmr-stak" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "virtualisation" "rkt" ] "The rkt module has been removed, it was archived by upstream")
     (mkRemovedOptionModule [ "services" "racoon" ] ''
       The racoon module has been removed, because the software project was abandoned upstream.
@@ -114,7 +118,6 @@ in
     (mkRemovedOptionModule [ "services" "virtuoso" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "openfire" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "riak" ] "The corresponding package was removed from nixpkgs.")
-    (mkRemovedOptionModule [ "services" "cryptpad" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "rtsp-simple-server" ] "Package has been completely rebranded by upstream as mediamtx, and thus the service and the package were renamed in NixOS as well.")
     (mkRemovedOptionModule [ "services" "prayer" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "restya-board" ] "The corresponding package was removed from nixpkgs.")
diff --git a/nixos/modules/security/apparmor/profiles.nix b/nixos/modules/security/apparmor/profiles.nix
index 0bf90a0086556..46af0f15f761b 100644
--- a/nixos/modules/security/apparmor/profiles.nix
+++ b/nixos/modules/security/apparmor/profiles.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, pkgs, ... }:
 let apparmor = config.security.apparmor; in
 {
 config.security.apparmor.packages = [ pkgs.apparmor-profiles ];
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index d74353f19b26e..e74858f4ce854 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -7,6 +7,13 @@ with lib;
 
 let
 
+  moduleSettingsType = with types; attrsOf (nullOr (oneOf [ bool str int pathInStore ]));
+  moduleSettingsDescription = ''
+    Boolean values render just the key if true, and nothing if false.
+    Null values are ignored.
+    All other values are rendered as key-value pairs.
+  '';
+
   mkRulesTypeOption = type: mkOption {
     # These options are experimental and subject to breaking changes without notice.
     description = ''
@@ -71,12 +78,12 @@ let
           '';
         };
         settings = mkOption {
-          type = with types; attrsOf (nullOr (oneOf [ bool str int pathInStore ]));
+          type = moduleSettingsType;
           default = {};
           description = ''
             Settings to add as `module-arguments`.
 
-            Boolean values render just the key if true, and nothing if false. Null values are ignored. All other values are rendered as key-value pairs.
+            ${moduleSettingsDescription}
           '';
         };
       };
@@ -92,6 +99,7 @@ let
     }));
   };
 
+  package = config.security.pam.package;
   parentConfig = config;
 
   pamOpts = { config, name, ... }: let cfg = config; in let config = parentConfig; in {
@@ -481,6 +489,18 @@ let
         package = mkPackageOption pkgs.plasma5Packages "kwallet-pam" {
           pkgsText = "pkgs.plasma5Packages";
         };
+
+        forceRun = mkEnableOption null // {
+          description = ''
+            The `force_run` option is used to tell the PAM module for KWallet
+            to forcefully run even if no graphical session (such as a GUI
+            display manager) is detected. This is useful for when you are
+            starting an X Session or a Wayland Session from a TTY. If you
+            intend to log-in from a TTY, it is recommended that you enable
+            this option **and** ensure that `plasma-kwallet-pam.service` is
+            started by `graphical-session.target`.
+          '';
+        };
       };
 
       sssdStrictAccess = mkOption {
@@ -630,7 +650,7 @@ let
           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
             config_file = "/etc/security/pam_mysql.conf";
           }; }
-          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; settings = {
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; settings = {
             ignore_unknown_user = true;
           }; }
           { name = "sss"; enable = config.services.sssd.enable; control = if cfg.sssdStrictAccess then "[default=bad success=ok user_unknown=ignore]" else "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
@@ -641,16 +661,16 @@ let
           # The required pam_unix.so module has to come after all the sufficient modules
           # because otherwise, the account lookup will fail if the user does not exist
           # locally, for example with MySQL- or LDAP-auth.
-          { name = "unix"; control = "required"; modulePath = "pam_unix.so"; }
+          { name = "unix"; control = "required"; modulePath = "${package}/lib/security/pam_unix.so"; }
         ];
 
         auth = autoOrderRules ([
           { name = "oslogin_login"; enable = cfg.googleOsLoginAuthentication; control = "[success=done perm_denied=die default=ignore]"; modulePath = "${pkgs.google-guest-oslogin}/lib/security/pam_oslogin_login.so"; }
-          { name = "rootok"; enable = cfg.rootOK; control = "sufficient"; modulePath = "pam_rootok.so"; }
-          { name = "wheel"; enable = cfg.requireWheel; control = "required"; modulePath = "pam_wheel.so"; settings = {
+          { name = "rootok"; enable = cfg.rootOK; control = "sufficient"; modulePath = "${package}/lib/security/pam_rootok.so"; }
+          { name = "wheel"; enable = cfg.requireWheel; control = "required"; modulePath = "${package}/lib/security/pam_wheel.so"; settings = {
             use_uid = true;
           }; }
-          { name = "faillock"; enable = cfg.logFailures; control = "required"; modulePath = "pam_faillock.so"; }
+          { name = "faillock"; enable = cfg.logFailures; control = "required"; modulePath = "${package}/lib/security/pam_faillock.so"; }
           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
             config_file = "/etc/security/pam_mysql.conf";
           }; }
@@ -660,11 +680,7 @@ let
           (let p11 = config.security.pam.p11; in { name = "p11"; enable = cfg.p11Auth; control = p11.control; modulePath = "${pkgs.pam_p11}/lib/security/pam_p11.so"; args = [
             "${pkgs.opensc}/lib/opensc-pkcs11.so"
           ]; })
-          (let u2f = config.security.pam.u2f; in { name = "u2f"; enable = cfg.u2fAuth; control = u2f.control; modulePath = "${pkgs.pam_u2f}/lib/security/pam_u2f.so"; settings = {
-            inherit (u2f) debug interactive cue origin;
-            authfile = u2f.authFile;
-            appid = u2f.appId;
-          }; })
+          (let u2f = config.security.pam.u2f; in { name = "u2f"; enable = cfg.u2fAuth; control = u2f.control; modulePath = "${pkgs.pam_u2f}/lib/security/pam_u2f.so"; inherit (u2f) settings; })
           (let ussh = config.security.pam.ussh; in { name = "ussh"; enable = config.security.pam.ussh.enable && cfg.usshAuth; control = ussh.control; modulePath = "${pkgs.pam_ussh}/lib/security/pam_ussh.so"; settings = {
             ca_file = ussh.caFile;
             authorized_principals = ussh.authorizedPrincipals;
@@ -707,7 +723,7 @@ let
               || cfg.zfs))
             [
               { name = "systemd_home-early"; enable = config.services.homed.enable; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
-              { name = "unix-early"; enable = cfg.unixAuth; control = "optional"; modulePath = "pam_unix.so"; settings = {
+              { name = "unix-early"; enable = cfg.unixAuth; control = "optional"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
                 nullok = cfg.allowNullPassword;
                 inherit (cfg) nodelay;
                 likeauth = true;
@@ -728,7 +744,7 @@ let
               { name = "gnupg"; enable = cfg.gnupg.enable; control = "optional"; modulePath = "${pkgs.pam_gnupg}/lib/security/pam_gnupg.so"; settings = {
                 store-only = cfg.gnupg.storeOnly;
               }; }
-              { name = "faildelay"; enable = cfg.failDelay.enable; control = "optional"; modulePath = "${pkgs.pam}/lib/security/pam_faildelay.so"; settings = {
+              { name = "faildelay"; enable = cfg.failDelay.enable; control = "optional"; modulePath = "${package}/lib/security/pam_faildelay.so"; settings = {
                 inherit (cfg.failDelay) delay;
               }; }
               { name = "google_authenticator"; enable = cfg.googleAuthenticator.enable; control = "required"; modulePath = "${pkgs.google-authenticator}/lib/security/pam_google_authenticator.so"; settings = {
@@ -737,7 +753,7 @@ let
               { name = "duo"; enable = cfg.duoSecurity.enable; control = "required"; modulePath = "${pkgs.duo-unix}/lib/security/pam_duo.so"; }
             ]) ++ [
           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
-          { name = "unix"; enable = cfg.unixAuth; control = "sufficient"; modulePath = "pam_unix.so"; settings = {
+          { name = "unix"; enable = cfg.unixAuth; control = "sufficient"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
             nullok = cfg.allowNullPassword;
             inherit (cfg) nodelay;
             likeauth = true;
@@ -747,7 +763,7 @@ let
           { name = "ldap"; enable = use_ldap; control = "sufficient"; modulePath = "${pam_ldap}/lib/security/pam_ldap.so"; settings = {
             use_first_pass = true;
           }; }
-          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; settings = {
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; settings = {
             ignore_unknown_user = true;
             use_first_pass = true;
           }; }
@@ -765,12 +781,12 @@ let
             action = "store";
             use_first_pass = true;
           }; }
-          { name = "deny"; control = "required"; modulePath = "pam_deny.so"; }
+          { name = "deny"; control = "required"; modulePath = "${package}/lib/security/pam_deny.so"; }
         ]);
 
         password = autoOrderRules [
           { name = "systemd_home"; enable = config.services.homed.enable; control = "sufficient"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
-          { name = "unix"; control = "sufficient"; modulePath = "pam_unix.so"; settings = {
+          { name = "unix"; control = "sufficient"; modulePath = "${package}/lib/security/pam_unix.so"; settings = {
             nullok = true;
             yescrypt = true;
           }; }
@@ -784,7 +800,7 @@ let
           { name = "mysql"; enable = cfg.mysqlAuth; control = "sufficient"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
             config_file = "/etc/security/pam_mysql.conf";
           }; }
-          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "sufficient"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; }
           { name = "sss"; enable = config.services.sssd.enable; control = "sufficient"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "sufficient"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; settings = {
             use_first_pass = true;
@@ -795,24 +811,24 @@ let
         ];
 
         session = autoOrderRules [
-          { name = "env"; enable = cfg.setEnvironment; control = "required"; modulePath = "pam_env.so"; settings = {
+          { name = "env"; enable = cfg.setEnvironment; control = "required"; modulePath = "${package}/lib/security/pam_env.so"; settings = {
             conffile = "/etc/pam/environment";
             readenv = 0;
           }; }
-          { name = "unix"; control = "required"; modulePath = "pam_unix.so"; }
-          { name = "loginuid"; enable = cfg.setLoginUid; control = if config.boot.isContainer then "optional" else "required"; modulePath = "pam_loginuid.so"; }
-          { name = "tty_audit"; enable = cfg.ttyAudit.enable; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_tty_audit.so"; settings = {
+          { name = "unix"; control = "required"; modulePath = "${package}/lib/security/pam_unix.so"; }
+          { name = "loginuid"; enable = cfg.setLoginUid; control = if config.boot.isContainer then "optional" else "required"; modulePath = "${package}/lib/security/pam_loginuid.so"; }
+          { name = "tty_audit"; enable = cfg.ttyAudit.enable; control = "required"; modulePath = "${package}/lib/security/pam_tty_audit.so"; settings = {
             open_only = cfg.ttyAudit.openOnly;
             enable = cfg.ttyAudit.enablePattern;
             disable = cfg.ttyAudit.disablePattern;
           }; }
           { name = "systemd_home"; enable = config.services.homed.enable; control = "required"; modulePath = "${config.systemd.package}/lib/security/pam_systemd_home.so"; }
-          { name = "mkhomedir"; enable = cfg.makeHomeDir; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_mkhomedir.so"; settings = {
+          { name = "mkhomedir"; enable = cfg.makeHomeDir; control = "required"; modulePath = "${package}/lib/security/pam_mkhomedir.so"; settings = {
             silent = true;
             skel = config.security.pam.makeHomeDir.skelDirectory;
             inherit (config.security.pam.makeHomeDir) umask;
           }; }
-          { name = "lastlog"; enable = cfg.updateWtmp; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_lastlog.so"; settings = {
+          { name = "lastlog"; enable = cfg.updateWtmp; control = "required"; modulePath = "${package}/lib/security/pam_lastlog.so"; settings = {
             silent = true;
           }; }
           { name = "ecryptfs"; enable = config.security.pam.enableEcryptfs; control = "optional"; modulePath = "${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"; }
@@ -820,11 +836,11 @@ let
           # Skips the pam_fscrypt module for systemd-user sessions which do not have a password
           # anyways.
           # See also https://github.com/google/fscrypt/issues/95
-          { name = "fscrypt-skip-systemd"; enable = config.security.pam.enableFscrypt; control = "[success=1 default=ignore]"; modulePath = "pam_succeed_if.so"; args = [
+          { name = "fscrypt-skip-systemd"; enable = config.security.pam.enableFscrypt; control = "[success=1 default=ignore]"; modulePath = "${package}/lib/security/pam_succeed_if.so"; args = [
             "service" "=" "systemd-user"
           ]; }
           { name = "fscrypt"; enable = config.security.pam.enableFscrypt; control = "optional"; modulePath = "${pkgs.fscrypt-experimental}/lib/security/pam_fscrypt.so"; }
-          { name = "zfs_key-skip-systemd"; enable = cfg.zfs; control = "[success=1 default=ignore]"; modulePath = "pam_succeed_if.so"; args = [
+          { name = "zfs_key-skip-systemd"; enable = cfg.zfs; control = "[success=1 default=ignore]"; modulePath = "${package}/lib/security/pam_succeed_if.so"; args = [
             "service" "=" "systemd-user"
           ]; }
           { name = "zfs_key"; enable = cfg.zfs; control = "optional"; modulePath = "${config.boot.zfs.package}/lib/security/pam_zfs_key.so"; settings = {
@@ -838,26 +854,26 @@ let
           { name = "mysql"; enable = cfg.mysqlAuth; control = "optional"; modulePath = "${pkgs.pam_mysql}/lib/security/pam_mysql.so"; settings = {
             config_file = "/etc/security/pam_mysql.conf";
           }; }
-          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "optional"; modulePath = "${pkgs.kanidm}/lib/pam_kanidm.so"; }
+          { name = "kanidm"; enable = config.services.kanidm.enablePam; control = "optional"; modulePath = "${config.services.kanidm.package}/lib/pam_kanidm.so"; }
           { name = "sss"; enable = config.services.sssd.enable; control = "optional"; modulePath = "${pkgs.sssd}/lib/security/pam_sss.so"; }
           { name = "krb5"; enable = config.security.pam.krb5.enable; control = "optional"; modulePath = "${pam_krb5}/lib/security/pam_krb5.so"; }
           { name = "otpw"; enable = cfg.otpwAuth; control = "optional"; modulePath = "${pkgs.otpw}/lib/security/pam_otpw.so"; }
           { name = "systemd"; enable = cfg.startSession; control = "optional"; modulePath = "${config.systemd.package}/lib/security/pam_systemd.so"; }
-          { name = "xauth"; enable = cfg.forwardXAuth; control = "optional"; modulePath = "pam_xauth.so"; settings = {
+          { name = "xauth"; enable = cfg.forwardXAuth; control = "optional"; modulePath = "${package}/lib/security/pam_xauth.so"; settings = {
             xauthpath = "${pkgs.xorg.xauth}/bin/xauth";
             systemuser = 99;
           }; }
-          { name = "limits"; enable = cfg.limits != []; control = "required"; modulePath = "${pkgs.pam}/lib/security/pam_limits.so"; settings = {
+          { name = "limits"; enable = cfg.limits != []; control = "required"; modulePath = "${package}/lib/security/pam_limits.so"; settings = {
             conf = "${makeLimitsConf cfg.limits}";
           }; }
-          { name = "motd"; enable = cfg.showMotd && (config.users.motd != null || config.users.motdFile != null); control = "optional"; modulePath = "${pkgs.pam}/lib/security/pam_motd.so"; settings = {
+          { name = "motd"; enable = cfg.showMotd && (config.users.motd != null || config.users.motdFile != null); control = "optional"; modulePath = "${package}/lib/security/pam_motd.so"; settings = {
             inherit motd;
           }; }
           { name = "apparmor"; enable = cfg.enableAppArmor && config.security.apparmor.enable; control = "optional"; modulePath = "${pkgs.apparmor-pam}/lib/security/pam_apparmor.so"; settings = {
             order = "user,group,default";
             debug = true;
           }; }
-          { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; }
+          { name = "kwallet"; enable = cfg.kwallet.enable; control = "optional"; modulePath = "${cfg.kwallet.package}/lib/security/pam_kwallet5.so"; settings = lib.mkIf cfg.kwallet.forceRun { force_run = true; }; }
           { name = "gnome_keyring"; enable = cfg.enableGnomeKeyring; control = "optional"; modulePath = "${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so"; settings = {
             auto_start = true;
           }; }
@@ -952,12 +968,20 @@ in
   imports = [
     (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
     (mkRenamedOptionModule [ "security" "pam" "enableSSHAgentAuth" ] [ "security" "pam" "sshAgentAuth" "enable" ])
+    (mkRenamedOptionModule [ "security" "pam" "u2f" "authFile" ] [ "security" "pam" "u2f" "settings" "authfile" ])
+    (mkRenamedOptionModule [ "security" "pam" "u2f" "appId" ] [ "security" "pam" "u2f" "settings" "appid" ])
+    (mkRenamedOptionModule [ "security" "pam" "u2f" "origin" ] [ "security" "pam" "u2f" "settings" "origin" ])
+    (mkRenamedOptionModule [ "security" "pam" "u2f" "debug" ] [ "security" "pam" "u2f" "settings" "debug" ])
+    (mkRenamedOptionModule [ "security" "pam" "u2f" "interactive" ] [ "security" "pam" "u2f" "settings" "interactive" ])
+    (mkRenamedOptionModule [ "security" "pam" "u2f" "cue" ] [ "security" "pam" "u2f" "settings" "cue" ])
   ];
 
   ###### interface
 
   options = {
 
+    security.pam.package = mkPackageOption pkgs "pam" { };
+
     security.pam.loginLimits = mkOption {
       default = [];
       type = limitsType;
@@ -1144,57 +1168,6 @@ in
         '';
       };
 
-      authFile = mkOption {
-        default = null;
-        type = with types; nullOr path;
-        description = ''
-          By default `pam-u2f` module reads the keys from
-          {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
-          {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
-          not set).
-
-          If you want to change auth file locations or centralize database (for
-          example use {file}`/etc/u2f-mappings`) you can set this
-          option.
-
-          File format is:
-          `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
-          This file can be generated using {command}`pamu2fcfg` command.
-
-          More information can be found [here](https://developers.yubico.com/pam-u2f/).
-        '';
-      };
-
-      appId = mkOption {
-        default = null;
-        type = with types; nullOr str;
-        description = ''
-            By default `pam-u2f` module sets the application
-            ID to `pam://$HOSTNAME`.
-
-            When using {command}`pamu2fcfg`, you can specify your
-            application ID with the `-i` flag.
-
-            More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
-        '';
-      };
-
-      origin = mkOption {
-        default = null;
-        type = with types; nullOr str;
-        description = ''
-            By default `pam-u2f` module sets the origin
-            to `pam://$HOSTNAME`.
-            Setting origin to an host independent value will allow you to
-            reuse credentials across machines
-
-            When using {command}`pamu2fcfg`, you can specify your
-            application ID with the `-o` flag.
-
-            More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
-        '';
-      };
-
       control = mkOption {
         default = "sufficient";
         type = types.enum [ "required" "requisite" "sufficient" "optional" ];
@@ -1209,33 +1182,104 @@ in
         '';
       };
 
-      debug = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Debug output to stderr.
-        '';
-      };
-
-      interactive = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Set to prompt a message and wait before testing the presence of a U2F device.
-          Recommended if your device doesn’t have a tactile trigger.
-        '';
-      };
-
-      cue = mkOption {
-        default = false;
-        type = types.bool;
+      settings = mkOption {
+        type = types.submodule {
+          freeformType = moduleSettingsType;
+
+          options = {
+            authfile = mkOption {
+              default = null;
+              type = with types; nullOr path;
+              description = ''
+                By default `pam-u2f` module reads the keys from
+                {file}`$XDG_CONFIG_HOME/Yubico/u2f_keys` (or
+                {file}`$HOME/.config/Yubico/u2f_keys` if XDG variable is
+                not set).
+
+                If you want to change auth file locations or centralize database (for
+                example use {file}`/etc/u2f-mappings`) you can set this
+                option.
+
+                File format is:
+                `username:first_keyHandle,first_public_key: second_keyHandle,second_public_key`
+                This file can be generated using {command}`pamu2fcfg` command.
+
+                More information can be found [here](https://developers.yubico.com/pam-u2f/).
+              '';
+            };
+
+            appid = mkOption {
+              default = null;
+              type = with types; nullOr str;
+              description = ''
+                  By default `pam-u2f` module sets the application
+                  ID to `pam://$HOSTNAME`.
+
+                  When using {command}`pamu2fcfg`, you can specify your
+                  application ID with the `-i` flag.
+
+                  More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
+              '';
+            };
+
+            origin = mkOption {
+              default = null;
+              type = with types; nullOr str;
+              description = ''
+                  By default `pam-u2f` module sets the origin
+                  to `pam://$HOSTNAME`.
+                  Setting origin to an host independent value will allow you to
+                  reuse credentials across machines
+
+                  When using {command}`pamu2fcfg`, you can specify your
+                  application ID with the `-o` flag.
+
+                  More information can be found [here](https://developers.yubico.com/pam-u2f/Manuals/pam_u2f.8.html)
+              '';
+            };
+
+            debug = mkOption {
+              default = false;
+              type = types.bool;
+              description = ''
+                Debug output to stderr.
+              '';
+            };
+
+            interactive = mkOption {
+              default = false;
+              type = types.bool;
+              description = ''
+                Set to prompt a message and wait before testing the presence of a U2F device.
+                Recommended if your device doesn’t have a tactile trigger.
+              '';
+            };
+
+            cue = mkOption {
+              default = false;
+              type = types.bool;
+              description = ''
+                By default `pam-u2f` module does not inform user
+                that he needs to use the u2f device, it just waits without a prompt.
+
+                If you set this option to `true`,
+                `cue` option is added to `pam-u2f`
+                module and reminder message will be displayed.
+              '';
+            };
+          };
+        };
+        default = { };
+        example = {
+          authfile = "/etc/u2f_keys";
+          authpending_file = "";
+          userpresence = 0;
+          pinverification = 1;
+        };
         description = ''
-          By default `pam-u2f` module does not inform user
-          that he needs to use the u2f device, it just waits without a prompt.
+          Options to pass to the PAM module.
 
-          If you set this option to `true`,
-          `cue` option is added to `pam-u2f`
-          module and reminder message will be displayed.
+          ${moduleSettingsDescription}
         '';
       };
     };
@@ -1486,9 +1530,9 @@ in
 
     environment.systemPackages =
       # Include the PAM modules in the system path mostly for the manpages.
-      [ pkgs.pam ]
+      [ package ]
       ++ optional config.users.ldap.enable pam_ldap
-      ++ optional config.services.kanidm.enablePam pkgs.kanidm
+      ++ optional config.services.kanidm.enablePam config.services.kanidm.package
       ++ optional config.services.sssd.enable pkgs.sssd
       ++ optionals config.security.pam.krb5.enable [pam_krb5 pam_ccreds]
       ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
@@ -1504,7 +1548,7 @@ in
         setuid = true;
         owner = "root";
         group = "root";
-        source = "${pkgs.pam}/bin/unix_chkpwd";
+        source = "${package}/bin/unix_chkpwd";
       };
     };
 
@@ -1545,11 +1589,6 @@ in
       lib.concatMapStrings
         (name: "r ${config.environment.etc."pam.d/${name}".source},\n")
         (attrNames config.security.pam.services) +
-      ''
-      mr ${getLib pkgs.pam}/lib/security/pam_filter/*,
-      mr ${getLib pkgs.pam}/lib/security/pam_*.so,
-      r ${getLib pkgs.pam}/lib/security/,
-      '' +
       (with lib; pipe config.security.pam.services [
         attrValues
         (catAttrs "rules")
@@ -1557,6 +1596,12 @@ in
         (concatMap attrValues)
         (filter (rule: rule.enable))
         (catAttrs "modulePath")
+        # TODO(@uninsane): replace this warning + filter with just an assertion
+        (map (modulePath: lib.warnIfNot
+          (hasPrefix "/" modulePath)
+          ''non-absolute PAM modulePath "${modulePath}" is unsupported by apparmor and will be treated as an error by future versions of nixpkgs; see <https://github.com/NixOS/nixpkgs/pull/314791>''
+          modulePath
+        ))
         (filter (hasPrefix "/"))
         unique
         (map (module: "mr ${module},"))
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 606b620ef120a..b5dae96d79c6b 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -255,11 +255,6 @@ in
         umount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/umount";
       };
 
-    boot.specialFileSystems.${parentWrapperDir} = {
-      fsType = "tmpfs";
-      options = [ "nodev" "mode=755" "size=${config.security.wrapperDirSize}" ];
-    };
-
     # Make sure our wrapperDir exports to the PATH env variable when
     # initializing the shell
     environment.extraInit = ''
@@ -275,6 +270,17 @@ in
       mrpx ${wrap.source},
     '') wrappers;
 
+    systemd.mounts = [{
+      where = parentWrapperDir;
+      what = "tmpfs";
+      type = "tmpfs";
+      options = lib.concatStringsSep "," ([
+        "nodev"
+        "mode=755"
+        "size=${config.security.wrapperDirSize}"
+      ]);
+    }];
+
     systemd.services.suid-sgid-wrappers = {
       description = "Create SUID/SGID Wrappers";
       wantedBy = [ "sysinit.target" ];
diff --git a/nixos/modules/services/accessibility/speechd.nix b/nixos/modules/services/accessibility/speechd.nix
new file mode 100644
index 0000000000000..165be86346ccb
--- /dev/null
+++ b/nixos/modules/services/accessibility/speechd.nix
@@ -0,0 +1,32 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.speechd;
+  inherit (lib)
+    getExe
+    mkEnableOption
+    mkIf
+    mkPackageOption
+    ;
+in
+{
+  options.services.speechd = {
+    # FIXME: figure out how to deprecate this EXTREMELY CAREFULLY
+    # default guessed conservatively in ../misc/graphical-desktop.nix
+    enable = mkEnableOption "speech-dispatcher speech synthesizer daemon";
+    package = mkPackageOption pkgs "speechd" { };
+  };
+
+  # FIXME: speechd 0.12 (or whatever the next version is)
+  # will support socket activation, so switch to that once it's out.
+  config = mkIf cfg.enable {
+    environment = {
+      systemPackages = [ cfg.package ];
+      sessionVariables.SPEECHD_CMD = getExe cfg.package;
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/alsa.nix b/nixos/modules/services/audio/alsa.nix
index b002cb1274ac3..fac0012b666d4 100644
--- a/nixos/modules/services/audio/alsa.nix
+++ b/nixos/modules/services/audio/alsa.nix
@@ -1,134 +1,38 @@
 # ALSA sound support.
 { config, lib, pkgs, ... }:
 
-with lib;
-
-let
-
-  inherit (pkgs) alsa-utils;
-
-  pulseaudioEnabled = config.hardware.pulseaudio.enable;
-
-in
-
 {
   imports = [
-    (mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ])
+    (lib.mkRemovedOptionModule [ "sound" ] "The option was heavily overloaded and can be removed from most configurations.")
   ];
 
-  ###### interface
-
-  options = {
-
-    sound = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable ALSA sound.
-        '';
-      };
-
-      enableOSSEmulation = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable ALSA OSS emulation (with certain cards sound mixing may not work!).
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        example = ''
-          defaults.pcm.!card 3
-        '';
-        description = ''
-          Set addition configuration for system-wide alsa.
-        '';
-      };
-
-      mediaKeys = {
-
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Whether to enable volume and capture control with keyboard media keys.
-
-            You want to leave this disabled if you run a desktop environment
-            like KDE, Gnome, Xfce, etc, as those handle such things themselves.
-            You might want to enable this if you run a minimalistic desktop
-            environment or work from bare linux ttys/framebuffers.
-
-            Enabling this will turn on {option}`services.actkbd`.
-          '';
-        };
-
-        volumeStep = mkOption {
-          type = types.str;
-          default = "1";
-          example = "1%";
-          description = ''
-            The value by which to increment/decrement volume on media keys.
-
-            See amixer(1) for allowed values.
-          '';
-        };
-
-      };
-
-    };
-
+  options.hardware.alsa.enablePersistence = lib.mkOption {
+    type = lib.types.bool;
+    default = false;
+    description = ''
+      Whether to enable ALSA sound card state saving on shutdown.
+      This is generally not necessary if you're using an external sound server.
+    '';
   };
 
-
-  ###### implementation
-
-  config = mkIf config.sound.enable {
-
-    environment.systemPackages = [ alsa-utils ];
-
-    environment.etc = mkIf (!pulseaudioEnabled && config.sound.extraConfig != "")
-      { "asound.conf".text = config.sound.extraConfig; };
-
+  config = lib.mkIf config.hardware.alsa.enablePersistence {
     # ALSA provides a udev rule for restoring volume settings.
-    services.udev.packages = [ alsa-utils ];
-
-    boot.kernelModules = optional config.sound.enableOSSEmulation "snd_pcm_oss";
-
-    systemd.services.alsa-store =
-      { description = "Store Sound Card State";
-        wantedBy = [ "multi-user.target" ];
-        unitConfig.RequiresMountsFor = "/var/lib/alsa";
-        unitConfig.ConditionVirtualization = "!systemd-nspawn";
-        serviceConfig = {
-          Type = "oneshot";
-          RemainAfterExit = true;
-          ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
-          ExecStart = "${alsa-utils}/sbin/alsactl restore --ignore";
-          ExecStop = "${alsa-utils}/sbin/alsactl store --ignore";
-        };
+    services.udev.packages = [ pkgs.alsa-utils ];
+
+    systemd.services.alsa-store = {
+      description = "Store Sound Card State";
+      wantedBy = [ "multi-user.target" ];
+      unitConfig = {
+        RequiresMountsFor = "/var/lib/alsa";
+        ConditionVirtualization = "!systemd-nspawn";
+      };
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
+        ExecStart = "${pkgs.alsa-utils}/sbin/alsactl restore --ignore";
+        ExecStop = "${pkgs.alsa-utils}/sbin/alsactl store --ignore";
       };
-
-    services.actkbd = mkIf config.sound.mediaKeys.enable {
-      enable = true;
-      bindings = [
-        # "Mute" media key
-        { keys = [ 113 ]; events = [ "key" ];       command = "${alsa-utils}/bin/amixer -q set Master toggle"; }
-
-        # "Lower Volume" media key
-        { keys = [ 114 ]; events = [ "key" "rep" ]; command = "${alsa-utils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}- unmute"; }
-
-        # "Raise Volume" media key
-        { keys = [ 115 ]; events = [ "key" "rep" ]; command = "${alsa-utils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}+ unmute"; }
-
-        # "Mic Mute" media key
-        { keys = [ 190 ]; events = [ "key" ];       command = "${alsa-utils}/bin/amixer -q set Capture toggle"; }
-      ];
     };
-
   };
-
 }
diff --git a/nixos/modules/services/audio/goxlr-utility.nix b/nixos/modules/services/audio/goxlr-utility.nix
index 6081b3707f54d..00aaa77a24d59 100644
--- a/nixos/modules/services/audio/goxlr-utility.nix
+++ b/nixos/modules/services/audio/goxlr-utility.nix
@@ -28,19 +28,30 @@ with lib;
     };
   };
 
-  config = mkIf config.services.goxlr-utility.enable
-    {
+  config =
+    let
+      goxlr-autostart = pkgs.stdenv.mkDerivation {
+        name = "autostart-goxlr-daemon";
+        priority = 5;
+
+        buildCommand = ''
+          mkdir -p $out/etc/xdg/autostart
+          cp ${cfg.package}/share/applications/goxlr-utility.desktop $out/etc/xdg/autostart/goxlr-daemon.desktop
+          chmod +w $out/etc/xdg/autostart/goxlr-daemon.desktop
+          echo "X-KDE-autostart-phase=2" >> $out/etc/xdg/autostart/goxlr-daemon.desktop
+          substituteInPlace $out/etc/xdg/autostart/goxlr-daemon.desktop \
+            --replace-fail goxlr-launcher goxlr-daemon
+        '';
+      };
+    in
+    mkIf config.services.goxlr-utility.enable {
       services.udev.packages = [ cfg.package ];
 
       xdg.autostart.enable = mkIf cfg.autoStart.xdg true;
       environment.systemPackages = mkIf cfg.autoStart.xdg
         [
           cfg.package
-          (pkgs.makeAutostartItem
-            {
-              name = "goxlr-utility";
-              package = cfg.package;
-            })
+          goxlr-autostart
         ];
     };
 
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
index 20ba091542fe3..a5cb0da29592e 100644
--- a/nixos/modules/services/audio/jack.nix
+++ b/nixos/modules/services/audio/jack.nix
@@ -122,7 +122,7 @@ in {
   config = mkMerge [
 
     (mkIf pcmPlugin {
-      sound.extraConfig = ''
+      environment.etc."alsa/conf.d/98-jack.conf".text = ''
         pcm_type.jack {
           libs.native = ${pkgs.alsa-plugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;
           ${lib.optionalString enable32BitAlsaPlugins
@@ -139,7 +139,7 @@ in {
     (mkIf loopback {
       boot.kernelModules = [ "snd-aloop" ];
       boot.kernelParams = [ "snd-aloop.index=${toString cfg.loopback.index}" ];
-      sound.extraConfig = cfg.loopback.config;
+      environment.etc."alsa/conf.d/99-jack-loopback.conf".text = cfg.loopback.config;
     })
 
     (mkIf cfg.jackd.enable {
diff --git a/nixos/modules/services/audio/jmusicbot.nix b/nixos/modules/services/audio/jmusicbot.nix
index 5507f4859058f..d1317facf273f 100644
--- a/nixos/modules/services/audio/jmusicbot.nix
+++ b/nixos/modules/services/audio/jmusicbot.nix
@@ -40,5 +40,5 @@ in
     };
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/audio/music-assistant.nix b/nixos/modules/services/audio/music-assistant.nix
new file mode 100644
index 0000000000000..90c0b41fc587f
--- /dev/null
+++ b/nixos/modules/services/audio/music-assistant.nix
@@ -0,0 +1,113 @@
+{
+  config,
+  lib,
+  pkgs,
+  utils,
+  ...
+}:
+
+let
+  inherit (lib)
+    mkIf
+    mkEnableOption
+    mkOption
+    mkPackageOption
+    types
+  ;
+
+  inherit (types)
+    listOf
+    enum
+    str
+  ;
+
+  cfg = config.services.music-assistant;
+
+  finalPackage = cfg.package.override {
+    inherit (cfg) providers;
+  };
+in
+
+{
+  meta.buildDocsInSandbox = false;
+
+  options.services.music-assistant = {
+    enable = mkEnableOption "Music Assistant";
+
+    package = mkPackageOption pkgs "music-assistant" { };
+
+    extraOptions = mkOption {
+      type = listOf str;
+      default = [ "--config" "/var/lib/music-assistant" ];
+      example = [
+        "--log-level"
+        "DEBUG"
+      ];
+      description = ''
+        List of extra options to pass to the music-assistant executable.
+      '';
+    };
+
+    providers = mkOption {
+      type = listOf (enum cfg.package.providerNames);
+      default = [];
+      example = [
+        "opensubsonic"
+        "snapcast"
+      ];
+      description = ''
+        List of provider names for which dependencies will be installed.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.music-assistant = {
+      description = "Music Assistant";
+      documentation = [ "https://music-assistant.io" ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        HOME = "/var/lib/music-assistant";
+        PYTHONPATH = finalPackage.pythonPath;
+      };
+
+      serviceConfig = {
+        ExecStart = utils.escapeSystemdExecArgs ([
+          (lib.getExe cfg.package)
+        ] ++ cfg.extraOptions);
+        DynamicUser = true;
+        StateDirectory = "music-assistant";
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = [ "" ];
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged @resources"
+        ];
+        RestrictSUIDSGID = true;
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index f79fb7a07d79b..e624dacf34bfc 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -37,6 +37,7 @@ let
         "&${key}=${value}";
     in
       "--stream.stream=\"${opt.type}://" + os opt.location + "?" + os' "name=" name
+        + os' "&sampleformat=" opt.sampleFormat + os' "&codec=" opt.codec
         + concatStrings (mapAttrsToList flatten opt.query) + "\"";
 
   optionalNull = val: ret:
diff --git a/nixos/modules/services/backup/borgmatic.nix b/nixos/modules/services/backup/borgmatic.nix
index 5da78828bfa83..4d744a735582b 100644
--- a/nixos/modules/services/backup/borgmatic.nix
+++ b/nixos/modules/services/backup/borgmatic.nix
@@ -26,8 +26,8 @@ let
     freeformType = settingsFormat.type;
     options = {
       source_directories = mkOption {
-        type = nullOr (listOf str);
-        default = null;
+        type = listOf str;
+        default = [];
         description = ''
           List of source directories and files to backup. Globs and tildes are
           expanded. Do not backslash spaces in path names.
@@ -35,8 +35,8 @@ let
         example = [ "/home" "/etc" "/var/log/syslog*" "/home/user/path with spaces" ];
       };
       repositories = mkOption {
-        type = nullOr (listOf repository);
-        default = null;
+        type = listOf repository;
+        default = [];
         description = ''
           A required list of local or remote repositories with paths and
           optional labels (which can be used with the --repository flag to
@@ -76,29 +76,42 @@ in
       default = { };
       type = types.attrsOf cfgType;
     };
+
+    enableConfigCheck = mkEnableOption "checking all configurations during build time" // { default = true; };
   };
 
-  config = mkIf cfg.enable {
+  config =
+    let
+      configFiles =
+        (optionalAttrs (cfg.settings != null) { "borgmatic/config.yaml".source = cfgfile; }) //
+        mapAttrs'
+          (name: value: nameValuePair
+            "borgmatic.d/${name}.yaml"
+            { source = settingsFormat.generate "${name}.yaml" value; })
+          cfg.configurations;
+      borgmaticCheck = name: f: pkgs.runCommandCC "${name} validation" { } ''
+            ${pkgs.borgmatic}/bin/borgmatic -c ${f.source} config validate
+            touch $out
+          '';
+    in
+      mkIf cfg.enable {
 
-    warnings = []
-      ++ optional (cfg.settings != null && cfg.settings ? location)
-        "`services.borgmatic.settings.location` is deprecated, please move your options out of sections to the global scope"
-      ++ optional (catAttrs "location" (attrValues cfg.configurations) != [])
-        "`services.borgmatic.configurations.<name>.location` is deprecated, please move your options out of sections to the global scope"
-    ;
+        warnings = []
+          ++ optional (cfg.settings != null && cfg.settings ? location)
+            "`services.borgmatic.settings.location` is deprecated, please move your options out of sections to the global scope"
+          ++ optional (catAttrs "location" (attrValues cfg.configurations) != [])
+            "`services.borgmatic.configurations.<name>.location` is deprecated, please move your options out of sections to the global scope"
+        ;
 
-    environment.systemPackages = [ pkgs.borgmatic ];
+        environment.systemPackages = [ pkgs.borgmatic ];
 
-    environment.etc = (optionalAttrs (cfg.settings != null) { "borgmatic/config.yaml".source = cfgfile; }) //
-      mapAttrs'
-        (name: value: nameValuePair
-          "borgmatic.d/${name}.yaml"
-          { source = settingsFormat.generate "${name}.yaml" value; })
-        cfg.configurations;
+        environment.etc = configFiles;
 
-    systemd.packages = [ pkgs.borgmatic ];
+        systemd.packages = [ pkgs.borgmatic ];
 
-    # Workaround: https://github.com/NixOS/nixpkgs/issues/81138
-    systemd.timers.borgmatic.wantedBy = [ "timers.target" ];
-  };
+        # Workaround: https://github.com/NixOS/nixpkgs/issues/81138
+        systemd.timers.borgmatic.wantedBy = [ "timers.target" ];
+
+        system.checks = mkIf cfg.enableConfigCheck (mapAttrsToList borgmaticCheck configFiles);
+      };
 }
diff --git a/nixos/modules/services/backup/btrbk.nix b/nixos/modules/services/backup/btrbk.nix
index 06ca4236eaf22..fa6c67ff7cbfa 100644
--- a/nixos/modules/services/backup/btrbk.nix
+++ b/nixos/modules/services/backup/btrbk.nix
@@ -204,10 +204,6 @@ in
   };
   config = mkIf (sshEnabled || serviceEnabled) {
 
-    warnings = optional (cfg.extraPackages != []) ''
-      extraPackages option will be deprecated in future releases. Programs required for compression are now automatically selected depending on services.btrbk.instances.<name>.settings.stream_compress option.
-    '';
-
     environment.systemPackages = [ pkgs.btrbk ] ++ cfg.extraPackages;
 
     security.sudo.extraRules = mkIf (sudo_doas == "sudo") [ sudoRule ];
diff --git a/nixos/modules/services/backup/duplicity.nix b/nixos/modules/services/backup/duplicity.nix
index 033d0cffd8d6e..46625ec5460e4 100644
--- a/nixos/modules/services/backup/duplicity.nix
+++ b/nixos/modules/services/backup/duplicity.nix
@@ -42,6 +42,28 @@ in
       '';
     };
 
+    includeFileList = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = /path/to/fileList.txt;
+      description = ''
+        File containing newline-separated list of paths to include into the
+        backups. See the FILE SELECTION section in {manpage}`duplicity(1)` for
+        details on the syntax.
+      '';
+    };
+
+    excludeFileList = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = /path/to/fileList.txt;
+      description = ''
+        File containing newline-separated list of paths to exclude into the
+        backups. See the FILE SELECTION section in {manpage}`duplicity(1)` for
+        details on the syntax.
+      '';
+    };
+
     targetUrl = mkOption {
       type = types.str;
       example = "s3://host:port/prefix";
@@ -154,6 +176,8 @@ in
             ${lib.optionalString (cfg.cleanup.maxIncr != null) "${dup} remove-all-inc-of-but-n-full ${toString cfg.cleanup.maxIncr} ${target} --force ${extra}"}
             exec ${dup} ${if cfg.fullIfOlderThan == "always" then "full" else "incr"} ${lib.escapeShellArgs (
               [ cfg.root cfg.targetUrl ]
+              ++ lib.optionals (cfg.includeFileList != null) [ "--include-filelist" cfg.includeFileList ]
+              ++ lib.optionals (cfg.excludeFileList != null) [ "--exclude-filelist" cfg.excludeFileList ]
               ++ concatMap (p: [ "--include" p ]) cfg.include
               ++ concatMap (p: [ "--exclude" p ]) cfg.exclude
               ++ (lib.optionals (cfg.fullIfOlderThan != "never" && cfg.fullIfOlderThan != "always") [ "--full-if-older-than" cfg.fullIfOlderThan ])
diff --git a/nixos/modules/services/backup/mysql-backup.nix b/nixos/modules/services/backup/mysql-backup.nix
index 00381be4b75d3..e3fa7f45844f1 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 ${optionalString cfg.singleTransaction "--single-transaction"} ${db} | ${gzip}/bin/gzip -c > $dest.tmp; then
+    if ${mariadb}/bin/mysqldump ${optionalString cfg.singleTransaction "--single-transaction"} ${db} | ${gzip}/bin/gzip -c ${cfg.gzipOptions} > $dest.tmp; then
       mv $dest.tmp $dest
       echo "Backed up to $dest"
     else
@@ -78,6 +78,14 @@ in
           Whether to create database dump in a single transaction
         '';
       };
+
+      gzipOptions = mkOption {
+        default = "--no-name --rsyncable";
+        type = types.str;
+        description = ''
+          Command line options to use when invoking `gzip`.
+        '';
+      };
     };
 
   };
diff --git a/nixos/modules/services/backup/restic-rest-server.nix b/nixos/modules/services/backup/restic-rest-server.nix
index 935907643bd2f..eb7b57800333e 100644
--- a/nixos/modules/services/backup/restic-rest-server.nix
+++ b/nixos/modules/services/backup/restic-rest-server.nix
@@ -15,7 +15,7 @@ in
       default = "8000";
       example = "127.0.0.1:8080";
       type = types.str;
-      description = "Listen on a specific IP address and port.";
+      description = "Listen on a specific IP address and port or unix socket.";
     };
 
     dataDir = mkOption {
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 8be2649189b95..a7c2ef2eacd5b 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -83,6 +83,15 @@ in
           '';
         };
 
+        inhibitsSleep = mkOption {
+          default = false;
+          type = types.bool;
+          example = true;
+          description = ''
+            Prevents the system from sleeping while backing up.
+          '';
+        };
+
         repository = mkOption {
           type = with types; nullOr str;
           default = null;
@@ -299,7 +308,14 @@ in
         (name: backup:
           let
             extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
-            resticCmd = "${backup.package}/bin/restic${extraOptions}";
+            inhibitCmd = concatStringsSep " " [
+              "${pkgs.systemd}/bin/systemd-inhibit"
+              "--mode='block'"
+              "--who='restic'"
+              "--what='sleep'"
+              "--why=${escapeShellArg "Scheduled backup ${name}"} "
+            ];
+            resticCmd = "${optionalString backup.inhibitsSleep inhibitCmd}${backup.package}/bin/restic${extraOptions}";
             excludeFlags = optional (backup.exclude != []) "--exclude-file=${pkgs.writeText "exclude-patterns" (concatStringsSep "\n" backup.exclude)}";
             filesFromTmpFile = "/run/restic-backups-${name}/includes";
             doBackup = (backup.dynamicFilesFrom != null) || (backup.paths != null && backup.paths != []);
@@ -355,10 +371,10 @@ in
                 ${pkgs.writeScript "backupPrepareCommand" backup.backupPrepareCommand}
               ''}
               ${optionalString (backup.initialize) ''
-                ${resticCmd} snapshots || ${resticCmd} init
+                ${resticCmd} cat config > /dev/null || ${resticCmd} init
               ''}
               ${optionalString (backup.paths != null && backup.paths != []) ''
-                cat ${pkgs.writeText "staticPaths" (concatStringsSep "\n" backup.paths)} >> ${filesFromTmpFile}
+                cat ${pkgs.writeText "staticPaths" (concatLines backup.paths)} >> ${filesFromTmpFile}
               ''}
               ${optionalString (backup.dynamicFilesFrom != null) ''
                 ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} >> ${filesFromTmpFile}
diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix
index dc5d8f09e069b..9e1abb85bfe54 100644
--- a/nixos/modules/services/backup/tsm.nix
+++ b/nixos/modules/services/backup/tsm.nix
@@ -90,7 +90,7 @@ in
       environment.HOME = "/var/lib/tsm-backup";
       serviceConfig = {
         # for exit status description see
-        # https://www.ibm.com/docs/en/storage-protect/8.1.22?topic=clients-client-return-codes
+        # https://www.ibm.com/docs/en/storage-protect/8.1.23?topic=clients-client-return-codes
         SuccessExitStatus = "4 8";
         # The `-se` option must come after the command.
         # The `-optfile` option suppresses a `dsm.opt`-not-found warning.
diff --git a/nixos/modules/services/blockchain/ethereum/lighthouse.nix b/nixos/modules/services/blockchain/ethereum/lighthouse.nix
index dcf56e187eaec..a5ace1a9450f3 100644
--- a/nixos/modules/services/blockchain/ethereum/lighthouse.nix
+++ b/nixos/modules/services/blockchain/ethereum/lighthouse.nix
@@ -192,7 +192,7 @@ in {
       };
 
       network = mkOption {
-        type = types.enum [ "mainnet" "prater" "goerli" "gnosis" "kiln" "ropsten" "sepolia" ];
+        type = types.enum [ "mainnet" "gnosis" "chiado" "sepolia" "holesky" ];
         default = "mainnet";
         description = ''
           The network to connect to. Mainnet is the default ethereum network.
diff --git a/nixos/modules/services/cluster/druid/default.nix b/nixos/modules/services/cluster/druid/default.nix
new file mode 100644
index 0000000000000..f28e5c90270cf
--- /dev/null
+++ b/nixos/modules/services/cluster/druid/default.nix
@@ -0,0 +1,296 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.druid;
+  inherit (lib)
+    concatStrings
+    concatStringsSep
+    mapAttrsToList
+    concatMap
+    attrByPath
+    mkIf
+    mkMerge
+    mkEnableOption
+    mkOption
+    types
+    mkPackageOption
+    ;
+
+  druidServiceOption = serviceName: {
+    enable = mkEnableOption serviceName;
+
+    restartIfChanged = mkOption {
+      type = types.bool;
+      description = ''
+        Automatically restart the service on config change.
+        This can be set to false to defer restarts on clusters running critical applications.
+        Please consider the security implications of inadvertently running an older version,
+        and the possibility of unexpected behavior caused by inconsistent versions across a cluster when disabling this option.
+      '';
+      default = false;
+    };
+
+    config = mkOption {
+      default = { };
+      type = types.attrsOf types.anything;
+      description = ''
+        (key=value) Configuration to be written to runtime.properties of the druid ${serviceName}
+        <https://druid.apache.org/docs/latest/configuration/index.html>
+      '';
+      example = {
+        "druid.plainTextPort" = "8082";
+        "druid.service" = "servicename";
+      };
+    };
+
+    jdk = mkPackageOption pkgs "JDK" { default = [ "jdk17_headless" ]; };
+
+    jvmArgs = mkOption {
+      type = types.str;
+      default = "";
+      description = "Arguments to pass to the JVM";
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Open firewall ports for ${serviceName}.";
+    };
+
+    internalConfig = mkOption {
+      default = { };
+      type = types.attrsOf types.anything;
+      internal = true;
+      description = "Internal Option to add to runtime.properties for ${serviceName}.";
+    };
+  };
+
+  druidServiceConfig =
+    {
+      name,
+      serviceOptions ? cfg."${name}",
+      allowedTCPPorts ? [ ],
+      tmpDirs ? [ ],
+      extraConfig ? { },
+    }:
+    (mkIf serviceOptions.enable (mkMerge [
+      {
+        systemd = {
+          services."druid-${name}" = {
+            after = [ "network.target" ];
+
+            description = "Druid ${name}";
+
+            wantedBy = [ "multi-user.target" ];
+
+            inherit (serviceOptions) restartIfChanged;
+
+            path = [
+              cfg.package
+              serviceOptions.jdk
+            ];
+
+            script =
+              let
+                cfgFile =
+                  fileName: properties:
+                  pkgs.writeTextDir fileName (
+                    concatStringsSep "\n" (mapAttrsToList (n: v: "${n}=${toString v}") properties)
+                  );
+
+                commonConfigFile = cfgFile "common.runtime.properties" cfg.commonConfig;
+
+                configFile = cfgFile "runtime.properties" (serviceOptions.config // serviceOptions.internalConfig);
+
+                extraClassPath = concatStrings (map (path: ":" + path) cfg.extraClassPaths);
+
+                extraConfDir = concatStrings (map (dir: ":" + dir + "/*") cfg.extraConfDirs);
+              in
+              ''
+                run-java -Dlog4j.configurationFile=file:${cfg.log4j} \
+                  -Ddruid.extensions.directory=${cfg.package}/extensions \
+                  -Ddruid.extensions.hadoopDependenciesDir=${cfg.package}/hadoop-dependencies \
+                  -classpath  ${commonConfigFile}:${configFile}:${cfg.package}/lib/\*${extraClassPath}${extraConfDir} \
+                  ${serviceOptions.jvmArgs} \
+                  org.apache.druid.cli.Main server ${name}
+              '';
+
+            serviceConfig = {
+              User = "druid";
+              SyslogIdentifier = "druid-${name}";
+              Restart = "always";
+            };
+          };
+
+          tmpfiles.rules = concatMap (x: [ "d ${x} 0755 druid druid" ]) (cfg.commonTmpDirs ++ tmpDirs);
+        };
+        networking.firewall.allowedTCPPorts = mkIf (attrByPath [
+          "openFirewall"
+        ] false serviceOptions) allowedTCPPorts;
+
+        users = {
+          users.druid = {
+            description = "Druid user";
+            group = "druid";
+            isNormalUser = true;
+          };
+          groups.druid = { };
+        };
+      }
+      extraConfig
+    ]));
+in
+{
+  options.services.druid = {
+    package = mkPackageOption pkgs "apache-druid" { default = [ "druid" ]; };
+
+    commonConfig = mkOption {
+      default = { };
+
+      type = types.attrsOf types.anything;
+
+      description = "(key=value) Configuration to be written to common.runtime.properties";
+
+      example = {
+        "druid.zk.service.host" = "localhost:2181";
+        "druid.metadata.storage.type" = "mysql";
+        "druid.metadata.storage.connector.connectURI" = "jdbc:mysql://localhost:3306/druid";
+        "druid.extensions.loadList" = ''[ "mysql-metadata-storage" ]'';
+      };
+    };
+
+    commonTmpDirs = mkOption {
+      default = [ "/var/log/druid/requests" ];
+      type = types.listOf types.str;
+      description = "Common List of directories used by druid processes";
+    };
+
+    log4j = mkOption {
+      type = types.path;
+      description = "Log4j Configuration for the druid process";
+    };
+
+    extraClassPaths = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = "Extra classpath to include in the jvm";
+    };
+
+    extraConfDirs = mkOption {
+      default = [ ];
+      type = types.listOf types.path;
+      description = "Extra Conf Dirs to include in the jvm";
+    };
+
+    overlord = druidServiceOption "Druid Overlord";
+
+    coordinator = druidServiceOption "Druid Coordinator";
+
+    broker = druidServiceOption "Druid Broker";
+
+    historical = (druidServiceOption "Druid Historical") // {
+      segmentLocations = mkOption {
+
+        default = null;
+
+        description = "Locations where the historical will store its data.";
+
+        type =
+          with types;
+          nullOr (
+            listOf (submodule {
+              options = {
+                path = mkOption {
+                  type = path;
+                  description = "the path to store the segments";
+                };
+
+                maxSize = mkOption {
+                  type = str;
+                  description = "Max size the druid historical can occupy";
+                };
+
+                freeSpacePercent = mkOption {
+                  type = float;
+                  default = 1.0;
+                  description = "Druid Historical will fail to write if it exceeds this value";
+                };
+              };
+            })
+          );
+
+      };
+    };
+
+    middleManager = druidServiceOption "Druid middleManager";
+    router = druidServiceOption "Druid Router";
+  };
+  config = mkMerge [
+    (druidServiceConfig rec {
+      name = "overlord";
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8090 cfg."${name}".config) ];
+    })
+
+    (druidServiceConfig rec {
+      name = "coordinator";
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8081 cfg."${name}".config) ];
+    })
+
+    (druidServiceConfig rec {
+      name = "broker";
+
+      tmpDirs = [ (attrByPath [ "druid.lookup.snapshotWorkingDir" ] "" cfg."${name}".config) ];
+
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8082 cfg."${name}".config) ];
+    })
+
+    (druidServiceConfig rec {
+      name = "historical";
+
+      tmpDirs = [
+        (attrByPath [ "druid.lookup.snapshotWorkingDir" ] "" cfg."${name}".config)
+      ] ++ (map (x: x.path) cfg."${name}".segmentLocations);
+
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8083 cfg."${name}".config) ];
+
+      extraConfig.services.druid.historical.internalConfig."druid.segmentCache.locations" = builtins.toJSON cfg.historical.segmentLocations;
+    })
+
+    (druidServiceConfig rec {
+      name = "middleManager";
+
+      tmpDirs = [
+        "/var/log/druid/indexer"
+      ] ++ [ (attrByPath [ "druid.indexer.task.baseTaskDir" ] "" cfg."${name}".config) ];
+
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8091 cfg."${name}".config) ];
+
+      extraConfig = {
+        services.druid.middleManager.internalConfig = {
+          "druid.indexer.runner.javaCommand" = "${cfg.middleManager.jdk}/bin/java";
+          "druid.indexer.runner.javaOpts" =
+            (attrByPath [ "druid.indexer.runner.javaOpts" ] "" cfg.middleManager.config)
+            + " -Dlog4j.configurationFile=file:${cfg.log4j}";
+        };
+
+        networking.firewall.allowedTCPPortRanges = mkIf cfg.middleManager.openFirewall [
+          {
+            from = attrByPath [ "druid.indexer.runner.startPort" ] 8100 cfg.middleManager.config;
+            to = attrByPath [ "druid.indexer.runner.endPort" ] 65535 cfg.middleManager.config;
+          }
+        ];
+      };
+    })
+
+    (druidServiceConfig rec {
+      name = "router";
+
+      allowedTCPPorts = [ (attrByPath [ "druid.plaintextPort" ] 8888 cfg."${name}".config) ];
+    })
+  ];
+
+}
diff --git a/nixos/modules/services/cluster/k3s/default.nix b/nixos/modules/services/cluster/k3s/default.nix
index 4d18d378d7944..83dfe20671470 100644
--- a/nixos/modules/services/cluster/k3s/default.nix
+++ b/nixos/modules/services/cluster/k3s/default.nix
@@ -17,6 +17,115 @@ let
       ]
       ++ config
     ) instruction;
+
+  manifestDir = "/var/lib/rancher/k3s/server/manifests";
+  chartDir = "/var/lib/rancher/k3s/server/static/charts";
+  imageDir = "/var/lib/rancher/k3s/agent/images";
+  containerdConfigTemplateFile = "/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl";
+
+  manifestModule =
+    let
+      mkTarget =
+        name: if (lib.hasSuffix ".yaml" name || lib.hasSuffix ".yml" name) then name else name + ".yaml";
+    in
+    lib.types.submodule (
+      {
+        name,
+        config,
+        options,
+        ...
+      }:
+      {
+        options = {
+          enable = lib.mkOption {
+            type = lib.types.bool;
+            default = true;
+            description = "Whether this manifest file should be generated.";
+          };
+
+          target = lib.mkOption {
+            type = lib.types.nonEmptyStr;
+            example = lib.literalExpression "manifest.yaml";
+            description = ''
+              Name of the symlink (relative to {file}`${manifestDir}`).
+              Defaults to the attribute name.
+            '';
+          };
+
+          content = lib.mkOption {
+            type = with lib.types; nullOr (either attrs (listOf attrs));
+            default = null;
+            description = ''
+              Content of the manifest file. A single attribute set will
+              generate a single document YAML file. A list of attribute sets
+              will generate multiple documents separated by `---` in a single
+              YAML file.
+            '';
+          };
+
+          source = lib.mkOption {
+            type = lib.types.path;
+            example = lib.literalExpression "./manifests/app.yaml";
+            description = ''
+              Path of the source `.yaml` file.
+            '';
+          };
+        };
+
+        config = {
+          target = lib.mkDefault (mkTarget name);
+          source = lib.mkIf (config.content != null) (
+            let
+              name' = "k3s-manifest-" + builtins.baseNameOf name;
+              docName = "k3s-manifest-doc-" + builtins.baseNameOf name;
+              yamlDocSeparator = builtins.toFile "yaml-doc-separator" "\n---\n";
+              mkYaml = name: x: (pkgs.formats.yaml { }).generate name x;
+              mkSource =
+                value:
+                if builtins.isList value then
+                  pkgs.concatText name' (
+                    lib.concatMap (x: [
+                      yamlDocSeparator
+                      (mkYaml docName x)
+                    ]) value
+                  )
+                else
+                  mkYaml name' value;
+            in
+            lib.mkDerivedConfig options.content mkSource
+          );
+        };
+      }
+    );
+
+  enabledManifests = with builtins; filter (m: m.enable) (attrValues cfg.manifests);
+  linkManifestEntry = m: "${pkgs.coreutils-full}/bin/ln -sfn ${m.source} ${manifestDir}/${m.target}";
+  linkImageEntry = image: "${pkgs.coreutils-full}/bin/ln -sfn ${image} ${imageDir}/${image.name}";
+  linkChartEntry =
+    let
+      mkTarget = name: if (lib.hasSuffix ".tgz" name) then name else name + ".tgz";
+    in
+    name: value:
+    "${pkgs.coreutils-full}/bin/ln -sfn ${value} ${chartDir}/${mkTarget (builtins.baseNameOf name)}";
+
+  activateK3sContent = pkgs.writeShellScript "activate-k3s-content" ''
+    ${lib.optionalString (
+      builtins.length enabledManifests > 0
+    ) "${pkgs.coreutils-full}/bin/mkdir -p ${manifestDir}"}
+    ${lib.optionalString (cfg.charts != { }) "${pkgs.coreutils-full}/bin/mkdir -p ${chartDir}"}
+    ${lib.optionalString (
+      builtins.length cfg.images > 0
+    ) "${pkgs.coreutils-full}/bin/mkdir -p ${imageDir}"}
+
+    ${builtins.concatStringsSep "\n" (map linkManifestEntry enabledManifests)}
+    ${builtins.concatStringsSep "\n" (lib.mapAttrsToList linkChartEntry cfg.charts)}
+    ${builtins.concatStringsSep "\n" (map linkImageEntry cfg.images)}
+
+    ${lib.optionalString (cfg.containerdConfigTemplate != null) ''
+      mkdir -p $(dirname ${containerdConfigTemplateFile})
+      ${pkgs.coreutils-full}/bin/ln -sfn ${pkgs.writeText "config.toml.tmpl" cfg.containerdConfigTemplate} ${containerdConfigTemplateFile}
+    ''}
+  '';
 in
 {
   imports = [ (removeOption [ "docker" ] "k3s docker option is no longer supported.") ];
@@ -103,9 +212,12 @@ in
 
     extraFlags = mkOption {
       description = "Extra flags to pass to the k3s command.";
-      type = types.str;
-      default = "";
-      example = "--no-deploy traefik --cluster-cidr 10.24.0.0/16";
+      type = with types; either str (listOf str);
+      default = [ ];
+      example = [
+        "--no-deploy traefik"
+        "--cluster-cidr 10.24.0.0/16"
+      ];
     };
 
     disableAgent = mkOption {
@@ -127,11 +239,219 @@ in
       default = null;
       description = "File path containing the k3s YAML config. This is useful when the config is generated (for example on boot).";
     };
+
+    manifests = mkOption {
+      type = types.attrsOf manifestModule;
+      default = { };
+      example = lib.literalExpression ''
+        deployment.source = ../manifests/deployment.yaml;
+        my-service = {
+          enable = false;
+          target = "app-service.yaml";
+          content = {
+            apiVersion = "v1";
+            kind = "Service";
+            metadata = {
+              name = "app-service";
+            };
+            spec = {
+              selector = {
+                "app.kubernetes.io/name" = "MyApp";
+              };
+              ports = [
+                {
+                  name = "name-of-service-port";
+                  protocol = "TCP";
+                  port = 80;
+                  targetPort = "http-web-svc";
+                }
+              ];
+            };
+          }
+        };
+
+        nginx.content = [
+          {
+            apiVersion = "v1";
+            kind = "Pod";
+            metadata = {
+              name = "nginx";
+              labels = {
+                "app.kubernetes.io/name" = "MyApp";
+              };
+            };
+            spec = {
+              containers = [
+                {
+                  name = "nginx";
+                  image = "nginx:1.14.2";
+                  ports = [
+                    {
+                      containerPort = 80;
+                      name = "http-web-svc";
+                    }
+                  ];
+                }
+              ];
+            };
+          }
+          {
+            apiVersion = "v1";
+            kind = "Service";
+            metadata = {
+              name = "nginx-service";
+            };
+            spec = {
+              selector = {
+                "app.kubernetes.io/name" = "MyApp";
+              };
+              ports = [
+                {
+                  name = "name-of-service-port";
+                  protocol = "TCP";
+                  port = 80;
+                  targetPort = "http-web-svc";
+                }
+              ];
+            };
+          }
+        ];
+      '';
+      description = ''
+        Auto-deploying manifests that are linked to {file}`${manifestDir}` before k3s starts.
+        Note that deleting manifest files will not remove or otherwise modify the resources
+        it created. Please use the the `--disable` flag or `.skip` files to delete/disable AddOns,
+        as mentioned in the [docs](https://docs.k3s.io/installation/packaged-components#disabling-manifests).
+        This option only makes sense on server nodes (`role = server`).
+        Read the [auto-deploying manifests docs](https://docs.k3s.io/installation/packaged-components#auto-deploying-manifests-addons)
+        for further information.
+      '';
+    };
+
+    charts = mkOption {
+      type = with types; attrsOf (either path package);
+      default = { };
+      example = lib.literalExpression ''
+        nginx = ../charts/my-nginx-chart.tgz;
+        redis = ../charts/my-redis-chart.tgz;
+      '';
+      description = ''
+        Packaged Helm charts that are linked to {file}`${chartDir}` before k3s starts.
+        The attribute name will be used as the link target (relative to {file}`${chartDir}`).
+        The specified charts will only be placed on the file system and made available to the
+        Kubernetes APIServer from within the cluster, you may use the
+        [k3s Helm controller](https://docs.k3s.io/helm#using-the-helm-controller)
+        to deploy the charts. This option only makes sense on server nodes
+        (`role = server`).
+      '';
+    };
+
+    containerdConfigTemplate = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = lib.literalExpression ''
+        # Base K3s config
+        {{ template "base" . }}
+
+        # Add a custom runtime
+        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes."custom"]
+          runtime_type = "io.containerd.runc.v2"
+        [plugins."io.containerd.grpc.v1.cri".containerd.runtimes."custom".options]
+          BinaryName = "/path/to/custom-container-runtime"
+      '';
+      description = ''
+        Config template for containerd, to be placed at
+        `/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl`.
+        See the K3s docs on [configuring containerd](https://docs.k3s.io/advanced#configuring-containerd).
+      '';
+    };
+
+    images = mkOption {
+      type = with types; listOf package;
+      default = [ ];
+      example = lib.literalExpression ''
+        [
+          (pkgs.dockerTools.pullImage {
+            imageName = "docker.io/bitnami/keycloak";
+            imageDigest = "sha256:714dfadc66a8e3adea6609bda350345bd3711657b7ef3cf2e8015b526bac2d6b";
+            sha256 = "0imblp0kw9vkcr7sp962jmj20fpmb3hvd3hmf4cs4x04klnq3k90";
+            finalImageTag = "21.1.2-debian-11-r0";
+          })
+
+          config.services.k3s.package.airgapImages
+        ]
+      '';
+      description = ''
+        List of derivations that provide container images.
+        All images are linked to {file}`${imageDir}` before k3s starts and consequently imported
+        by the k3s agent. Consider importing the k3s airgap images archive of the k3s package in
+        use, if you want to pre-provision this node with all k3s container images. This option
+        only makes sense on nodes with an enabled agent.
+      '';
+    };
+
+    gracefulNodeShutdown = {
+      enable = lib.mkEnableOption ''
+        graceful node shutdowns where the kubelet attempts to detect
+        node system shutdown and terminates pods running on the node. See the
+        [documentation](https://kubernetes.io/docs/concepts/cluster-administration/node-shutdown/#graceful-node-shutdown)
+        for further information.
+      '';
+
+      shutdownGracePeriod = lib.mkOption {
+        type = lib.types.nonEmptyStr;
+        default = "30s";
+        example = "1m30s";
+        description = ''
+          Specifies the total duration that the node should delay the shutdown by. This is the total
+          grace period for pod termination for both regular and critical pods.
+        '';
+      };
+
+      shutdownGracePeriodCriticalPods = lib.mkOption {
+        type = lib.types.nonEmptyStr;
+        default = "10s";
+        example = "15s";
+        description = ''
+          Specifies the duration used to terminate critical pods during a node shutdown. This should be
+          less than `shutdownGracePeriod`.
+        '';
+      };
+    };
+
+    extraKubeletConfig = lib.mkOption {
+      type = with lib.types; attrsOf anything;
+      default = { };
+      example = {
+        podsPerCore = 3;
+        memoryThrottlingFactor = 0.69;
+        containerLogMaxSize = "5Mi";
+      };
+      description = ''
+        Extra configuration to add to the kubelet's configuration file. The subset of the kubelet's
+        configuration that can be configured via a file is defined by the
+        [KubeletConfiguration](https://kubernetes.io/docs/reference/config-api/kubelet-config.v1beta1/)
+        struct. See the
+        [documentation](https://kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file/)
+        for further information.
+      '';
+    };
   };
 
   # implementation
 
   config = mkIf cfg.enable {
+    warnings =
+      (lib.optional (cfg.role != "server" && cfg.manifests != { })
+        "k3s: Auto deploying manifests are only installed on server nodes (role == server), they will be ignored by this node."
+      )
+      ++ (lib.optional (cfg.role != "server" && cfg.charts != { })
+        "k3s: Helm charts are only made available to the cluster on server nodes (role == server), they will be ignored by this node."
+      )
+      ++ (lib.optional (cfg.disableAgent && cfg.images != [ ])
+        "k3s: Images are only imported on nodes with an enabled agent, they will be ignored by this node"
+      );
+
     assertions = [
       {
         assertion = cfg.role == "agent" -> (cfg.configPath != null || cfg.serverAddr != "");
@@ -154,41 +474,60 @@ in
 
     environment.systemPackages = [ config.services.k3s.package ];
 
-    systemd.services.k3s = {
-      description = "k3s service";
-      after = [
-        "firewall.service"
-        "network-online.target"
-      ];
-      wants = [
-        "firewall.service"
-        "network-online.target"
-      ];
-      wantedBy = [ "multi-user.target" ];
-      path = optional config.boot.zfs.enabled config.boot.zfs.package;
-      serviceConfig = {
-        # See: https://github.com/rancher/k3s/blob/dddbd16305284ae4bd14c0aade892412310d7edc/install.sh#L197
-        Type = if cfg.role == "agent" then "exec" else "notify";
-        KillMode = "process";
-        Delegate = "yes";
-        Restart = "always";
-        RestartSec = "5s";
-        LimitNOFILE = 1048576;
-        LimitNPROC = "infinity";
-        LimitCORE = "infinity";
-        TasksMax = "infinity";
-        EnvironmentFile = cfg.environmentFile;
-        ExecStart = concatStringsSep " \\\n " (
-          [ "${cfg.package}/bin/k3s ${cfg.role}" ]
-          ++ (optional cfg.clusterInit "--cluster-init")
-          ++ (optional cfg.disableAgent "--disable-agent")
-          ++ (optional (cfg.serverAddr != "") "--server ${cfg.serverAddr}")
-          ++ (optional (cfg.token != "") "--token ${cfg.token}")
-          ++ (optional (cfg.tokenFile != null) "--token-file ${cfg.tokenFile}")
-          ++ (optional (cfg.configPath != null) "--config ${cfg.configPath}")
-          ++ [ cfg.extraFlags ]
+    systemd.services.k3s =
+      let
+        kubeletParams =
+          (lib.optionalAttrs (cfg.gracefulNodeShutdown.enable) {
+            inherit (cfg.gracefulNodeShutdown) shutdownGracePeriod shutdownGracePeriodCriticalPods;
+          })
+          // cfg.extraKubeletConfig;
+        kubeletConfig = (pkgs.formats.yaml { }).generate "k3s-kubelet-config" (
+          {
+            apiVersion = "kubelet.config.k8s.io/v1beta1";
+            kind = "KubeletConfiguration";
+          }
+          // kubeletParams
         );
+      in
+      {
+        description = "k3s service";
+        after = [
+          "firewall.service"
+          "network-online.target"
+        ];
+        wants = [
+          "firewall.service"
+          "network-online.target"
+        ];
+        wantedBy = [ "multi-user.target" ];
+        path = optional config.boot.zfs.enabled config.boot.zfs.package;
+        serviceConfig = {
+          # See: https://github.com/rancher/k3s/blob/dddbd16305284ae4bd14c0aade892412310d7edc/install.sh#L197
+          Type = if cfg.role == "agent" then "exec" else "notify";
+          KillMode = "process";
+          Delegate = "yes";
+          Restart = "always";
+          RestartSec = "5s";
+          LimitNOFILE = 1048576;
+          LimitNPROC = "infinity";
+          LimitCORE = "infinity";
+          TasksMax = "infinity";
+          EnvironmentFile = cfg.environmentFile;
+          ExecStartPre = activateK3sContent;
+          ExecStart = concatStringsSep " \\\n " (
+            [ "${cfg.package}/bin/k3s ${cfg.role}" ]
+            ++ (optional cfg.clusterInit "--cluster-init")
+            ++ (optional cfg.disableAgent "--disable-agent")
+            ++ (optional (cfg.serverAddr != "") "--server ${cfg.serverAddr}")
+            ++ (optional (cfg.token != "") "--token ${cfg.token}")
+            ++ (optional (cfg.tokenFile != null) "--token-file ${cfg.tokenFile}")
+            ++ (optional (cfg.configPath != null) "--config ${cfg.configPath}")
+            ++ (optional (kubeletParams != { }) "--kubelet-arg=config=${kubeletConfig}")
+            ++ (lib.flatten cfg.extraFlags)
+          );
+        };
       };
-    };
   };
+
+  meta.maintainers = lib.teams.k3s.members;
 }
diff --git a/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixos/modules/services/cluster/kubernetes/apiserver.nix
index fe9dacb8b93d5..81e359e0e642a 100644
--- a/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -159,10 +159,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gates.";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     kubeletClientCaFile = mkOption {
@@ -349,8 +349,8 @@ in
                 "--etcd-certfile=${cfg.etcd.certFile}"} \
               ${optionalString (cfg.etcd.keyFile != null)
                 "--etcd-keyfile=${cfg.etcd.keyFile}"} \
-              ${optionalString (cfg.featureGates != [])
-                "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+              ${optionalString (cfg.featureGates != {})
+                "--feature-gates=${(concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates)))}"} \
               ${optionalString (cfg.basicAuthFile != null)
                 "--basic-auth-file=${cfg.basicAuthFile}"} \
               ${optionalString (cfg.kubeletClientCaFile != null)
diff --git a/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
index 453043e507d97..b427de22bf896 100644
--- a/nixos/modules/services/cluster/kubernetes/controller-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -44,10 +44,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gates.";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes controller manager";
@@ -121,8 +121,8 @@ in
           --bind-address=${cfg.bindAddress} \
           ${optionalString (cfg.clusterCidr!=null)
             "--cluster-cidr=${cfg.clusterCidr}"} \
-          ${optionalString (cfg.featureGates != [])
-            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          ${optionalString (cfg.featureGates != {})
+            "--feature-gates=${concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates))}"} \
           --kubeconfig=${top.lib.mkKubeConfig "kube-controller-manager" cfg.kubeconfig} \
           --leader-elect=${boolToString cfg.leaderElect} \
           ${optionalString (cfg.rootCaFile!=null)
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 01760ffbc72da..208b2a864f024 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -61,13 +61,13 @@ let
   etcdEndpoints = ["https://${cfg.masterAddress}:2379"];
 
   mkCert = { name, CN, hosts ? [], fields ? {}, action ? "",
-             privateKeyOwner ? "kubernetes" }: rec {
+             privateKeyOwner ? "kubernetes", privateKeyGroup ? "kubernetes" }: rec {
     inherit name caCert CN hosts fields action;
     cert = secret name;
     key = secret "${name}-key";
     privateKeyOptions = {
       owner = privateKeyOwner;
-      group = "nogroup";
+      group = privateKeyGroup;
       mode = "0600";
       path = key;
     };
@@ -155,8 +155,8 @@ in {
 
     featureGates = mkOption {
       description = "List set of feature gates.";
-      default = [];
-      type = types.listOf types.str;
+      default = {};
+      type = types.attrsOf types.bool;
     };
 
     masterAddress = mkOption {
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index f36edeaf64ceb..fd9df556e7ec2 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -65,7 +65,7 @@ let
     // lib.optionalAttrs (cfg.tlsKeyFile != null)   { tlsPrivateKeyFile = cfg.tlsKeyFile; }
     // lib.optionalAttrs (cfg.clusterDomain != "")  { clusterDomain = cfg.clusterDomain; }
     // lib.optionalAttrs (cfg.clusterDns != "")     { clusterDNS = [ cfg.clusterDns ] ; }
-    // lib.optionalAttrs (cfg.featureGates != [])   { featureGates = cfg.featureGates; }
+    // lib.optionalAttrs (cfg.featureGates != {})   { featureGates = cfg.featureGates; }
   ));
 
   manifestPath = "kubernetes/manifests";
@@ -185,10 +185,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gate";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     healthz = {
diff --git a/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixos/modules/services/cluster/kubernetes/proxy.nix
index c09e7695f2a42..2e3fdc87b4396 100644
--- a/nixos/modules/services/cluster/kubernetes/proxy.nix
+++ b/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -30,10 +30,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gates.";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     hostname = mkOption {
@@ -69,8 +69,8 @@ in
           --bind-address=${cfg.bindAddress} \
           ${optionalString (top.clusterCidr!=null)
             "--cluster-cidr=${top.clusterCidr}"} \
-          ${optionalString (cfg.featureGates != [])
-            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          ${optionalString (cfg.featureGates != {})
+            "--feature-gates=${concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates))}"} \
           --hostname-override=${cfg.hostname} \
           --kubeconfig=${top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig} \
           ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
diff --git a/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixos/modules/services/cluster/kubernetes/scheduler.nix
index da2f39226a249..6fb90469c706b 100644
--- a/nixos/modules/services/cluster/kubernetes/scheduler.nix
+++ b/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -26,10 +26,10 @@ in
     };
 
     featureGates = mkOption {
-      description = "List set of feature gates";
+      description = "Attribute set of feature gates.";
       default = top.featureGates;
       defaultText = literalExpression "config.${otop.featureGates}";
-      type = listOf str;
+      type = attrsOf bool;
     };
 
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes scheduler";
@@ -67,8 +67,8 @@ in
         Slice = "kubernetes.slice";
         ExecStart = ''${top.package}/bin/kube-scheduler \
           --bind-address=${cfg.address} \
-          ${optionalString (cfg.featureGates != [])
-            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          ${optionalString (cfg.featureGates != {})
+            "--feature-gates=${concatStringsSep "," (builtins.attrValues (mapAttrs (n: v: "${n}=${trivial.boolToString v}") cfg.featureGates))}"} \
           --kubeconfig=${top.lib.mkKubeConfig "kube-scheduler" cfg.kubeconfig} \
           --leader-elect=${boolToString cfg.leaderElect} \
           --secure-port=${toString cfg.port} \
diff --git a/nixos/modules/services/cluster/patroni/default.nix b/nixos/modules/services/cluster/patroni/default.nix
index d1a165603fdaa..3b563bb89fffb 100644
--- a/nixos/modules/services/cluster/patroni/default.nix
+++ b/nixos/modules/services/cluster/patroni/default.nix
@@ -10,6 +10,15 @@ let
   configFile = format.generate configFileName cfg.settings;
 in
 {
+  imports = [
+    (lib.mkRemovedOptionModule [ "services" "patroni" "raft" ] ''
+      Raft has been deprecated by upstream.
+    '')
+    (lib.mkRemovedOptionModule [ "services" "patroni" "raftPort" ] ''
+      Raft has been deprecated by upstream.
+    '')
+  ];
+
   options.services.patroni = {
 
     enable = mkEnableOption "Patroni";
@@ -68,7 +77,7 @@ in
       type = types.path;
       default = "/var/lib/patroni";
       description = ''
-        Folder where Patroni data will be written, used by Raft as well if enabled.
+        Folder where Patroni data will be written, this is where the pgpass password file will be written.
       '';
     };
 
@@ -120,22 +129,6 @@ in
       '';
     };
 
-    raft = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        This will configure Patroni to use its own RAFT implementation instead of using a dedicated DCS.
-      '';
-    };
-
-    raftPort = mkOption {
-      type = types.port;
-      default = 5010;
-      description = ''
-        The port on which RAFT listens.
-      '';
-    };
-
     softwareWatchdog = mkOption {
       type = types.bool;
       default = false;
@@ -178,12 +171,6 @@ in
         connect_address = "${cfg.nodeIp}:${toString cfg.restApiPort}";
       };
 
-      raft = mkIf cfg.raft {
-        data_dir = "${cfg.dataDir}/raft";
-        self_addr = "${cfg.nodeIp}:5010";
-        partner_addrs = map (ip: ip + ":5010") cfg.otherNodesIps;
-      };
-
       postgresql = {
         listen = "${cfg.nodeIp}:${toString cfg.postgresqlPort}";
         connect_address = "${cfg.nodeIp}:${toString cfg.postgresqlPort}";
@@ -235,7 +222,7 @@ in
             KillMode = "process";
           }
           (mkIf (cfg.postgresqlDataDir == "/var/lib/postgresql/${cfg.postgresqlPackage.psqlSchema}" && cfg.dataDir == "/var/lib/patroni") {
-            StateDirectory = "patroni patroni/raft postgresql postgresql/${cfg.postgresqlPackage.psqlSchema}";
+            StateDirectory = "patroni postgresql postgresql/${cfg.postgresqlPackage.psqlSchema}";
             StateDirectoryMode = "0750";
           })
         ];
@@ -251,7 +238,6 @@ in
     environment.systemPackages = [
       pkgs.patroni
       cfg.postgresqlPackage
-      (mkIf cfg.raft pkgs.python310Packages.pysyncobj)
     ];
 
     environment.etc."${configFileName}".source = configFile;
diff --git a/nixos/modules/services/cluster/rke2/default.nix b/nixos/modules/services/cluster/rke2/default.nix
index 9ddbd299fdf8d..51b849ebcc802 100644
--- a/nixos/modules/services/cluster/rke2/default.nix
+++ b/nixos/modules/services/cluster/rke2/default.nix
@@ -241,7 +241,7 @@ in
       "kernel.panic_on_oops" = 1;
     };
 
-    systemd.services.rke2 = {
+    systemd.services."rke2-${cfg.role}" = {
       description = "Rancher Kubernetes Engine v2";
       documentation = [ "https://github.com/rancher/rke2#readme" ];
       after = [ "network-online.target" ];
diff --git a/nixos/modules/services/continuous-integration/buildkite-agents.nix b/nixos/modules/services/continuous-integration/buildkite-agents.nix
index fc30172c64999..eb7b7a1707854 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agents.nix
+++ b/nixos/modules/services/continuous-integration/buildkite-agents.nix
@@ -205,6 +205,8 @@ in
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg";
         User = "buildkite-agent-${name}";
+        # Workaround https://github.com/buildkite/agent/issues/2916
+        PrivateTmp = lib.mkDefault true;
         RestartSec = 5;
         Restart = "on-failure";
         TimeoutSec = 10;
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 1771ca0b980b9..34e9b6200783d 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -40,6 +40,7 @@ let
 
   cfg = config.services.gitlab-runner;
   hasDocker = config.virtualisation.docker.enable;
+  hasPodman = config.virtualisation.podman.enable && config.virtualisation.podman.dockerSocket.enable;
 
   /* The whole logic of this module is to diff the hashes of the desired vs existing runners
   The hash is recorded in the runner's name because we can't do better yet
@@ -137,8 +138,10 @@ let
             "--builds-dir ${service.buildsDir}"
             ++ optional (service.cloneUrl != null)
             "--clone-url ${service.cloneUrl}"
-            ++ optional (service.preCloneScript != null)
-            "--pre-clone-script ${service.preCloneScript}"
+            ++ optional (service.preGetSourcesScript != null)
+            "--pre-get-sources-script ${service.preGetSourcesScript}"
+            ++ optional (service.postGetSourcesScript != null)
+            "--post-get-sources-script ${service.postGetSourcesScript}"
             ++ optional (service.preBuildScript != null)
             "--pre-build-script ${service.preBuildScript}"
             ++ optional (service.postBuildScript != null)
@@ -495,13 +498,20 @@ in {
               Whitelist allowed services.
             '';
           };
-          preCloneScript = mkOption {
+          preGetSourcesScript = mkOption {
             type = types.nullOr types.path;
             default = null;
             description = ''
               Runner-specific command script executed before code is pulled.
             '';
           };
+          postGetSourcesScript = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = ''
+              Runner-specific command script executed after code is pulled.
+            '';
+          };
           preBuildScript = mkOption {
             type = types.nullOr types.path;
             default = null;
@@ -693,8 +703,11 @@ in {
       description = "Gitlab Runner";
       documentation = [ "https://docs.gitlab.com/runner/" ];
       after = [ "network.target" ]
-        ++ optional hasDocker "docker.service";
-      requires = optional hasDocker "docker.service";
+        ++ optional hasDocker "docker.service"
+        ++ optional hasPodman "podman.service";
+
+      requires = optional hasDocker "docker.service"
+        ++ optional hasPodman "podman.service";
       wantedBy = [ "multi-user.target" ];
       environment = config.networking.proxy.envVars // {
         HOME = "/var/lib/gitlab-runner";
@@ -720,7 +733,8 @@ in {
         # Make sure to restart service or changes won't apply.
         DynamicUser = true;
         StateDirectory = "gitlab-runner";
-        SupplementaryGroups = optional hasDocker "docker";
+        SupplementaryGroups = optional hasDocker "docker"
+          ++ optional hasPodman "podman";
         ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure";
         ExecStart = "${startScript}/bin/gitlab-runner-start";
         ExecReload = "!${configureScript}/bin/gitlab-runner-configure";
diff --git a/nixos/modules/services/databases/monetdb.nix b/nixos/modules/services/databases/monetdb.nix
index 5025eb30369b4..ee24cf2b0fc20 100644
--- a/nixos/modules/services/databases/monetdb.nix
+++ b/nixos/modules/services/databases/monetdb.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.monetdb;
 
 in {
-  meta.maintainers = with maintainers; [ StillerHarpo primeos ];
+  meta.maintainers = with maintainers; [ StillerHarpo ];
 
   ###### interface
   options = {
diff --git a/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index ad88a4f589a20..7a3f408aa98e4 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -64,14 +64,7 @@ in {
       servers = mkOption {
         type = with types; attrsOf (submodule ({ config, name, ... }: {
           options = {
-            enable = mkEnableOption ''
-              Redis server.
-
-              Note that the NixOS module for Redis disables kernel support
-              for Transparent Huge Pages (THP),
-              because this features causes major performance problems for Redis,
-              e.g. (https://redis.io/topics/latency)
-            '';
+            enable = mkEnableOption "Redis server";
 
             user = mkOption {
               type = types.str;
diff --git a/nixos/modules/services/databases/surrealdb.nix b/nixos/modules/services/databases/surrealdb.nix
index 08a6cca043ca4..2118312d5a1b5 100644
--- a/nixos/modules/services/databases/surrealdb.nix
+++ b/nixos/modules/services/databases/surrealdb.nix
@@ -58,7 +58,7 @@ in {
     environment.systemPackages = [ cfg.package ] ;
 
     systemd.services.surrealdb = {
-      description = "A scalable, distributed, collaborative, document-graph database, for the realtime web ";
+      description = "A scalable, distributed, collaborative, document-graph database, for the realtime web";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
 
diff --git a/nixos/modules/services/desktop-managers/lomiri.nix b/nixos/modules/services/desktop-managers/lomiri.nix
index 0b871aa38183e..bd43b4c1cfdae 100644
--- a/nixos/modules/services/desktop-managers/lomiri.nix
+++ b/nixos/modules/services/desktop-managers/lomiri.nix
@@ -21,8 +21,12 @@ in {
         history-service
         libusermetrics
         lomiri
+        lomiri-calculator-app
+        lomiri-camera-app
+        lomiri-clock-app
         lomiri-download-manager
         lomiri-filemanager-app
+        lomiri-polkit-agent
         lomiri-schemas # exposes some required dbus interfaces
         lomiri-session # wrappers to properly launch the session
         lomiri-sounds
@@ -35,7 +39,8 @@ in {
         morph-browser
         qtmir # not having its desktop file for Xwayland available causes any X11 application to crash the session
         suru-icon-theme
-        # telephony-service # currently broken: https://github.com/NixOS/nixpkgs/pull/314043
+        telephony-service
+        teleports
       ]);
       variables = {
         # To override the keyboard layouts in Lomiri
@@ -59,7 +64,7 @@ in {
 
     fonts.packages = with pkgs; [
       # Applications tend to default to Ubuntu font
-      ubuntu_font_family
+      ubuntu-classic
     ];
 
     # Copy-pasted basic stuff
@@ -84,7 +89,7 @@ in {
       ] ++ lib.optionals (config.hardware.pulseaudio.enable || config.services.pipewire.pulse.enable) [
         ayatana-indicator-sound
       ]) ++ (with pkgs.lomiri; [
-        # telephony-service # currently broken: https://github.com/NixOS/nixpkgs/pull/314043
+        telephony-service
       ] ++ lib.optionals config.networking.networkmanager.enable [
         lomiri-indicator-network
       ]);
@@ -145,6 +150,18 @@ in {
           ExecStart = "${pkgs.lomiri.lomiri-url-dispatcher}/libexec/lomiri-url-dispatcher/lomiri-update-directory /run/current-system/sw/share/lomiri-url-dispatcher/urls/";
         };
       };
+
+      "lomiri-polkit-agent" = rec {
+        description = "Lomiri Polkit agent";
+        wantedBy = [ "lomiri.service" "lomiri-full-greeter.service" "lomiri-full-shell.service" "lomiri-greeter.service" "lomiri-shell.service" ];
+        after = [ "graphical-session.target" ];
+        partOf = wantedBy;
+        serviceConfig = {
+          Type = "simple";
+          Restart = "always";
+          ExecStart = "${pkgs.lomiri.lomiri-polkit-agent}/libexec/lomiri-polkit-agent/policykit-agent";
+        };
+      };
     };
 
     systemd.services = {
diff --git a/nixos/modules/services/desktop-managers/plasma6.nix b/nixos/modules/services/desktop-managers/plasma6.nix
index 01a5bfa1dee27..3b36d3a905d06 100644
--- a/nixos/modules/services/desktop-managers/plasma6.nix
+++ b/nixos/modules/services/desktop-managers/plasma6.nix
@@ -213,6 +213,7 @@ in {
     };
 
     programs.gnupg.agent.pinentryPackage = mkDefault pkgs.pinentry-qt;
+    programs.kde-pim.enable = mkDefault true;
     programs.ssh.askPassword = mkDefault "${kdePackages.ksshaskpass.out}/bin/ksshaskpass";
 
     # Enable helpful DBus services.
@@ -237,6 +238,8 @@ in {
     systemd.packages = [kdePackages.drkonqi];
     systemd.services."drkonqi-coredump-processor@".wantedBy = ["systemd-coredump@.service"];
 
+    xdg.icons.enable = true;
+
     xdg.portal.enable = true;
     xdg.portal.extraPortals = [kdePackages.xdg-desktop-portal-kde];
     xdg.portal.configPackages = mkDefault [kdePackages.xdg-desktop-portal-kde];
@@ -253,6 +256,7 @@ in {
       extraPackages = with kdePackages; [
         breeze-icons
         kirigami
+        libplasma
         plasma5support
         qtsvg
         qtvirtualkeyboard
diff --git a/nixos/modules/services/desktops/ayatana-indicators.nix b/nixos/modules/services/desktops/ayatana-indicators.nix
index 613a2f03ea054..5a3d4eed8f65f 100644
--- a/nixos/modules/services/desktops/ayatana-indicators.nix
+++ b/nixos/modules/services/desktops/ayatana-indicators.nix
@@ -1,7 +1,8 @@
-{ config
-, pkgs
-, lib
-, ...
+{
+  config,
+  pkgs,
+  lib,
+  ...
 }:
 
 let
@@ -32,26 +33,27 @@ in
     environment = {
       systemPackages = cfg.packages;
 
-      pathsToLink = [
-        "/share/ayatana"
-      ];
+      pathsToLink = [ "/share/ayatana" ];
     };
 
     # libayatana-common's ayatana-indicators.target with explicit Wants & Before to bring up requested indicator services
-    systemd.user.targets."ayatana-indicators" =
+    systemd.user.targets =
       let
-        indicatorServices = lib.lists.flatten
-          (map
-            (pkg:
-              (map (ind: "${ind}.service") pkg.passthru.ayatana-indicators))
-            cfg.packages);
+        indicatorServices = lib.lists.flatten (
+          map (pkg: (map (ind: "${ind}.service") pkg.passthru.ayatana-indicators)) cfg.packages
+        );
       in
-      {
-        description = "Target representing the lifecycle of the Ayatana Indicators. Each indicator should be bound to it in its individual service file";
-        partOf = [ "graphical-session.target" ];
-        wants = indicatorServices;
-        before = indicatorServices;
-      };
+      lib.attrsets.mapAttrs
+        (_: desc: {
+          description = "Target representing the lifecycle of the ${desc}. Each indicator should be bound to it in its individual service file";
+          partOf = [ "graphical-session.target" ];
+          wants = indicatorServices;
+          before = indicatorServices;
+        })
+        {
+          ayatana-indicators = "Ayatana Indicators";
+          lomiri-indicators = "Ayatana/Lomiri Indicators that shall be run in Lomiri";
+        };
   };
 
   meta.maintainers = with lib.maintainers; [ OPNA2608 ];
diff --git a/nixos/modules/services/desktops/espanso.nix b/nixos/modules/services/desktops/espanso.nix
index a2c4b77b90464..4a9bff9267a43 100644
--- a/nixos/modules/services/desktops/espanso.nix
+++ b/nixos/modules/services/desktops/espanso.nix
@@ -3,7 +3,7 @@
 with lib;
 let cfg = config.services.espanso;
 in {
-  meta = { maintainers = with lib.maintainers; [ numkem ]; };
+  meta = { maintainers = with lib.maintainers; [ n8henrie numkem ]; };
 
   options = {
     services.espanso = {
diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index cda0a17d04758..b386bafcfe6f5 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -15,6 +15,8 @@ in {
   options = {
     services.flatpak = {
       enable = mkEnableOption "flatpak";
+
+      package = mkPackageOption pkgs "flatpak" { };
     };
   };
 
@@ -28,16 +30,16 @@ in {
       }
     ];
 
-    environment.systemPackages = [ pkgs.flatpak ];
+    environment.systemPackages = [ cfg.package ];
 
     security.polkit.enable = true;
 
     fonts.fontDir.enable = true;
 
-    services.dbus.packages = [ pkgs.flatpak ];
+    services.dbus.packages = [ cfg.package ];
 
-    systemd.packages = [ pkgs.flatpak ];
-    systemd.tmpfiles.packages = [ pkgs.flatpak ];
+    systemd.packages = [ cfg.package ];
+    systemd.tmpfiles.packages = [ cfg.package ];
 
     environment.profiles = [
       "$HOME/.local/share/flatpak/exports"
diff --git a/nixos/modules/services/development/livebook.nix b/nixos/modules/services/development/livebook.nix
index c7a6e35375791..8590836352013 100644
--- a/nixos/modules/services/development/livebook.nix
+++ b/nixos/modules/services/development/livebook.nix
@@ -89,6 +89,13 @@ in
         EnvironmentFile = cfg.environmentFile;
         ExecStart = "${cfg.package}/bin/livebook start";
         KillMode = "mixed";
+
+        # Fix for the issue described here:
+        # https://github.com/livebook-dev/livebook/issues/2691
+        #
+        # Without this, the livebook service fails to start and gets
+        # stuck running a `cat /dev/urandom | tr | fold` pipeline.
+        IgnoreSIGPIPE = false;
       };
       environment = mapAttrs (name: value:
         if isBool value then boolToString value else toString value)
@@ -98,5 +105,8 @@ in
     };
   };
 
-  meta.doc = ./livebook.md;
+  meta = {
+    doc = ./livebook.md;
+    maintainers = with lib.maintainers; [ munksgaard scvalex ];
+  };
 }
diff --git a/nixos/modules/services/display-managers/default.nix b/nixos/modules/services/display-managers/default.nix
index 9a7bd6c84b15b..894370199e79e 100644
--- a/nixos/modules/services/display-managers/default.nix
+++ b/nixos/modules/services/display-managers/default.nix
@@ -204,7 +204,8 @@ in
           noDmUsed = !(dmConf.gdm.enable
                     || cfg.sddm.enable
                     || dmConf.xpra.enable
-                    || dmConf.lightdm.enable);
+                    || dmConf.lightdm.enable
+                    || cfg.ly.enable);
       in lib.mkIf noDmUsed (lib.mkDefault false);
 
     systemd.services.display-manager = {
diff --git a/nixos/modules/services/display-managers/ly.nix b/nixos/modules/services/display-managers/ly.nix
new file mode 100644
index 0000000000000..5d42467a6cd8a
--- /dev/null
+++ b/nixos/modules/services/display-managers/ly.nix
@@ -0,0 +1,147 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  dmcfg = config.services.displayManager;
+  xcfg = config.services.xserver;
+  xdmcfg = xcfg.displayManager;
+  cfg = config.services.displayManager.ly;
+  xEnv = config.systemd.services.display-manager.environment;
+
+  ly = cfg.package;
+
+  iniFmt = pkgs.formats.iniWithGlobalSection { };
+
+  inherit (lib)
+    concatMapStrings
+    attrNames
+    getAttr
+    mkIf
+    mkOption
+    mkEnableOption
+    mkPackageOption
+    ;
+
+  xserverWrapper = pkgs.writeShellScript "xserver-wrapper" ''
+    ${concatMapStrings (n: ''
+      export ${n}="${getAttr n xEnv}"
+    '') (attrNames xEnv)}
+    exec systemd-cat -t xserver-wrapper ${xdmcfg.xserverBin} ${toString xdmcfg.xserverArgs} "$@"
+  '';
+
+  defaultConfig = {
+    shutdown_cmd = "/run/current-system/systemd/bin/systemctl poweroff";
+    restart_cmd = "/run/current-system/systemd/bin/systemctl reboot";
+    tty = 2;
+    service_name = "ly";
+    path = "/run/current-system/sw/bin";
+    term_reset_cmd = "${pkgs.ncurses}/bin/tput reset";
+    term_restore_cursor_cmd = "${pkgs.ncurses}/bin/tput cnorm";
+    mcookie_cmd = "/run/current-system/sw/bin/mcookie";
+    waylandsessions = "${dmcfg.sessionData.desktops}/share/wayland-sessions";
+    wayland_cmd = dmcfg.sessionData.wrapper;
+    xsessions = "${dmcfg.sessionData.desktops}/share/xsessions";
+    xauth_cmd = lib.optionalString xcfg.enable "${pkgs.xorg.xauth}/bin/xauth";
+    x_cmd = lib.optionalString xcfg.enable xserverWrapper;
+    x_cmd_setup = dmcfg.sessionData.wrapper;
+  };
+
+  finalConfig = defaultConfig // cfg.settings;
+
+  cfgFile = iniFmt.generate "config.ini" { globalSection = finalConfig; };
+
+in
+{
+  options = {
+    services.displayManager.ly = {
+      enable = mkEnableOption "ly as the display manager";
+
+      package = mkPackageOption pkgs [ "ly" ] { };
+
+      settings = mkOption {
+        type =
+          with lib.types;
+          attrsOf (oneOf [
+            str
+            int
+            bool
+          ]);
+        default = { };
+        example = {
+          load = false;
+          save = false;
+        };
+        description = ''
+          Extra settings merged in and overwriting defaults in config.ini.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = !dmcfg.autoLogin.enable;
+        message = ''
+          ly doesn't support auto login.
+        '';
+      }
+    ];
+
+    security.pam.services.ly = {
+      startSession = true;
+      unixAuth = true;
+    };
+
+    environment = {
+      etc."ly/config.ini".source = cfgFile;
+      systemPackages = [ ly ];
+
+      pathsToLink = [ "/share/ly" ];
+    };
+
+    services = {
+      dbus.packages = [ ly ];
+
+      displayManager = {
+        enable = true;
+        execCmd = "exec /run/current-system/sw/bin/ly";
+      };
+
+      xserver = {
+        # To enable user switching, allow ly to allocate TTYs/displays dynamically.
+        tty = null;
+        display = null;
+      };
+    };
+
+    systemd = {
+      # We're not using the upstream unit, so copy these:
+      # https://github.com/fairyglade/ly/blob/master/res/ly.service
+      services.display-manager = {
+        after = [
+          "systemd-user-sessions.service"
+          "plymouth-quit-wait.service"
+          "getty@tty${toString finalConfig.tty}.service"
+        ];
+
+        conflicts = [ "getty@tty7.service" ];
+
+        serviceConfig = {
+          Type = "idle";
+          StandardInput = "tty";
+          TTYPath = "/dev/tty${toString finalConfig.tty}";
+          TTYReset = "yes";
+          TTYVHangup = "yes";
+        };
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ vonfry ];
+}
diff --git a/nixos/modules/services/finance/odoo.nix b/nixos/modules/services/finance/odoo.nix
index 45fb9c7c2397a..074617d35e3d9 100644
--- a/nixos/modules/services/finance/odoo.nix
+++ b/nixos/modules/services/finance/odoo.nix
@@ -20,6 +20,17 @@ in
         description = "Odoo addons.";
       };
 
+      autoInit = mkEnableOption "automatically initialize the DB";
+
+      autoInitExtraFlags = mkOption {
+        type = with types; listOf str;
+        default = [ ];
+        example = literalExpression /*nix*/ ''
+          [ "--without-demo=all" ]
+        '';
+        description = "Extra flags passed to odoo when run for the first time by autoInit";
+      };
+
       settings = mkOption {
         type = format.type;
         default = {};
@@ -84,8 +95,11 @@ in
     };
 
     services.odoo.settings.options = {
+      data_dir = "/var/lib/private/odoo/data";
       proxy_mode = cfg.domain != null;
-    };
+    } // (lib.optionalAttrs (cfg.addons != []) {
+      addons_path = concatMapStringsSep "," escapeShellArg cfg.addons;
+    });
 
     users.users.odoo = {
       isSystemUser = true;
@@ -101,12 +115,40 @@ in
       path = [ config.services.postgresql.package ];
 
       requires = [ "postgresql.service" ];
-      script = "HOME=$STATE_DIRECTORY ${cfg.package}/bin/odoo ${optionalString (cfg.addons != []) "--addons-path=${concatMapStringsSep "," escapeShellArg cfg.addons}"} -c ${cfgFile}";
 
       serviceConfig = {
+        ExecStart = "${cfg.package}/bin/odoo";
+        ExecStartPre = pkgs.writeShellScript "odoo-start-pre.sh" (
+          ''
+          set -euo pipefail
+
+          cd "$STATE_DIRECTORY"
+
+          # Auto-migrate old deployments
+          if [[ -d .local/share/Odoo ]]; then
+            echo "pre-start: migrating state directory from $STATE_DIRECTORY/.local/share/Odoo to $STATE_DIRECTORY/data"
+            mv .local/share/Odoo ./data
+            rmdir .local/share
+            rmdir .local
+          fi
+          ''
+          + (lib.optionalString cfg.autoInit
+          ''
+          echo "pre-start: auto-init"
+          INITIALIZED="${cfg.settings.options.data_dir}/.odoo.initialized"
+          if [ ! -e "$INITIALIZED" ]; then
+            ${cfg.package}/bin/odoo  --init=INIT --database=odoo --db_user=odoo --stop-after-init ${concatStringsSep " " cfg.autoInitExtraFlags}
+            touch "$INITIALIZED"
+          fi
+          '')
+          + "echo pre-start: OK"
+        );
         DynamicUser = true;
         User = "odoo";
         StateDirectory = "odoo";
+        Environment = [
+          "ODOO_RC=${cfgFile}"
+        ];
       };
     };
 
diff --git a/nixos/modules/services/games/archisteamfarm.nix b/nixos/modules/services/games/archisteamfarm.nix
index 7062332db34ab..630329175968d 100644
--- a/nixos/modules/services/games/archisteamfarm.nix
+++ b/nixos/modules/services/games/archisteamfarm.nix
@@ -164,11 +164,8 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    services.archisteamfarm = {
-      # TODO: drop with 24.11
-      dataDir = lib.mkIf (lib.versionAtLeast config.system.stateVersion "24.05") (lib.mkDefault "/var/lib/asf");
-      settings.IPC = lib.mkIf (!cfg.web-ui.enable) false;
-    };
+    # TODO: drop with 24.11
+    services.archisteamfarm.dataDir = lib.mkIf (lib.versionAtLeast config.system.stateVersion "24.05") (lib.mkDefault "/var/lib/asf");
 
     users = {
       users.archisteamfarm = {
diff --git a/nixos/modules/services/games/terraria.nix b/nixos/modules/services/games/terraria.nix
index 57417b614f713..4c32cc92a1b94 100644
--- a/nixos/modules/services/games/terraria.nix
+++ b/nixos/modules/services/games/terraria.nix
@@ -19,15 +19,28 @@ let
     (boolFlag "secure" cfg.secure)
     (boolFlag "noupnp" cfg.noUPnP)
   ];
-  stopScript = pkgs.writeScript "terraria-stop" ''
-    #!${pkgs.runtimeShell}
 
+  tmuxCmd = "${lib.getExe pkgs.tmux} -S ${lib.escapeShellArg cfg.dataDir}/terraria.sock";
+
+  stopScript = pkgs.writeShellScript "terraria-stop" ''
     if ! [ -d "/proc/$1" ]; then
       exit 0
     fi
 
-    ${getBin pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/terraria.sock send-keys Enter exit Enter
-    ${getBin pkgs.coreutils}/bin/tail --pid="$1" -f /dev/null
+    lastline=$(${tmuxCmd} capture-pane -p | grep . | tail -n1)
+
+    # If the service is not configured to auto-start a world, it will show the world selection prompt
+    # If the last non-empty line on-screen starts with "Choose World", we know the prompt is open
+    if [[ "$lastline" =~ ^'Choose World' ]]; then
+      # In this case, nothing needs to be saved, so we can kill the process
+      ${tmuxCmd} kill-session
+    else
+      # Otherwise, we send the `exit` command
+      ${tmuxCmd} send-keys Enter exit Enter
+    fi
+
+    # Wait for the process to stop
+    tail --pid="$1" -f /dev/null
   '';
 in
 {
@@ -152,7 +165,7 @@ in
         Type = "forking";
         GuessMainPID = true;
         UMask = 007;
-        ExecStart = "${getBin pkgs.tmux}/bin/tmux -S ${cfg.dataDir}/terraria.sock new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}";
+        ExecStart = "${tmuxCmd} new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}";
         ExecStop = "${stopScript} $MAINPID";
       };
     };
diff --git a/nixos/modules/services/hardware/display.md b/nixos/modules/services/hardware/display.md
new file mode 100644
index 0000000000000..019c4cf146eb8
--- /dev/null
+++ b/nixos/modules/services/hardware/display.md
@@ -0,0 +1,130 @@
+# Customizing display configuration {#module-hardware-display}
+
+This section describes how to customize display configuration using:
+- kernel modes
+- EDID files
+
+Example situations it can help you with:
+- display controllers (external hardware) not advertising EDID at all,
+- misbehaving graphics drivers,
+- loading custom display configuration before the Display Manager is running,
+
+## Forcing display modes {#module-hardware-display-modes}
+
+In case of very wrong monitor controller and/or video driver combination you can
+[force the display to be enabled](https://mjmwired.net/kernel/Documentation/fb/modedb.txt#41)
+and skip some driver-side checks by adding `video=<OUTPUT>:e` to `boot.kernelParams`.
+This is exactly the case with [`amdgpu` drivers](https://gitlab.freedesktop.org/drm/amd/-/issues/615#note_1987392)
+
+```nix
+{
+  # force enabled output to skip `amdgpu` checks
+  hardware.display.outputs."DP-1".mode = "e";
+  # completely disable output no matter what is connected to it
+  hardware.display.outputs."VGA-2".mode = "d";
+
+  /* equals
+  boot.kernelParams = [ "video=DP-1:e" "video=VGA-2:d" ];
+  */
+}
+```
+
+## Crafting custom EDID files {#module-hardware-display-edid-custom}
+
+To make custom EDID binaries discoverable you should first create a derivation storing them at
+`$out/lib/firmware/edid/` and secondly add that derivation to `hardware.display.edid.packages` NixOS option:
+
+```nix
+{
+  hardware.display.edid.packages = [
+    (pkgs.runCommand "edid-custom" {} ''
+       mkdir -p $out/lib/firmware/edid
+       base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
+       <insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
+       EOF
+       base64 -d > "$out/lib/firmware/edid/custom2.bin" <<'EOF'
+       <insert your base64 encoded EDID file here `base64 < /sys/class/drm/card1-.../edid`>
+       EOF
+    '')
+  ];
+}
+```
+
+There are 2 options significantly easing preparation of EDID files:
+- `hardware.display.edid.linuxhw`
+- `hardware.display.edid.modelines`
+
+## Assigning EDID files to displays {#module-hardware-display-edid-assign}
+
+To assign available custom EDID binaries to your monitor (video output) use `hardware.display.outputs."<NAME>".edid` option.
+Under the hood it adds `drm.edid_firmware` entry to `boot.kernelParams` NixOS option for each configured output:
+
+```nix
+{
+  hardware.display.outputs."VGA-1".edid = "custom1.bin";
+  hardware.display.outputs."VGA-2".edid = "custom2.bin";
+  /* equals:
+  boot.kernelParams = [ "drm.edid_firmware=VGA-1:edid/custom1.bin,VGA-2:edid/custom2.bin" ];
+  */
+}
+```
+
+## Pulling files from linuxhw/EDID database {#module-hardware-display-edid-linuxhw}
+
+`hardware.display.edid.linuxhw` utilizes `pkgs.linuxhw-edid-fetcher` to extract EDID files
+from https://github.com/linuxhw/EDID based on simple string/regexp search identifying exact entries:
+
+```nix
+{
+  hardware.display.edid.linuxhw."PG278Q_2014" = [ "PG278Q" "2014" ];
+
+  /* equals:
+  hardware.display.edid.packages = [
+    (pkgs.linuxhw-edid-fetcher.override {
+      displays = {
+        "PG278Q_2014" = [ "PG278Q" "2014" ];
+      };
+    })
+  ];
+  */
+}
+```
+
+
+## Using XFree86 Modeline definitions {#module-hardware-display-edid-modelines}
+
+`hardware.display.edid.modelines` utilizes `pkgs.edid-generator` package allowing you to
+conveniently use [`XFree86 Modeline`](https://en.wikipedia.org/wiki/XFree86_Modeline) entries as EDID binaries:
+
+```nix
+{
+  hardware.display.edid.modelines."PG278Q_60" = "    241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync";
+  hardware.display.edid.modelines."PG278Q_120" = "   497.75   2560 2608 2640 2720   1440 1443 1448 1525   +hsync -vsync";
+
+  /* equals:
+  hardware.display.edid.packages = [
+    (pkgs.edid-generator.overrideAttrs {
+      clean = true;
+      modelines = ''
+        Modeline "PG278Q_60"      241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync
+        Modeline "PG278Q_120"     497.75   2560 2608 2640 2720   1440 1443 1448 1525   +hsync -vsync
+      '';
+    })
+  ];
+  */
+}
+```
+
+## Complete example for Asus PG278Q {#module-hardware-display-pg278q}
+
+And finally this is a complete working example for a 2014 (first) batch of [Asus PG278Q monitor with `amdgpu` drivers](https://gitlab.freedesktop.org/drm/amd/-/issues/615#note_1987392):
+
+```nix
+{
+  hardware.display.edid.modelines."PG278Q_60" = "   241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync";
+  hardware.display.edid.modelines."PG278Q_120" = "  497.75   2560 2608 2640 2720   1440 1443 1448 1525   +hsync -vsync";
+
+  hardware.display.outputs."DP-1".edid = "PG278Q_60.bin";
+  hardware.display.outputs."DP-1".mode = "e";
+}
+```
diff --git a/nixos/modules/services/hardware/display.nix b/nixos/modules/services/hardware/display.nix
new file mode 100644
index 0000000000000..3b3118f132e92
--- /dev/null
+++ b/nixos/modules/services/hardware/display.nix
@@ -0,0 +1,193 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.hardware.display;
+in
+{
+  meta.doc = ./display.md;
+  meta.maintainers = with lib.maintainers; [
+    nazarewk
+  ];
+
+  options = {
+    hardware.display.edid.enable = lib.mkOption {
+      type = with lib.types; bool;
+      default = cfg.edid.packages != null;
+      defaultText = lib.literalExpression "config.hardware.display.edid.packages != null";
+      description = ''
+        Enables handling of EDID files
+      '';
+    };
+
+    hardware.display.edid.packages = lib.mkOption {
+      type = with lib.types; listOf package;
+      default = [ ];
+      description = ''
+        List of packages containing EDID binary files at `$out/lib/firmware/edid`.
+        Such files will be available for use in `drm.edid_firmware` kernel
+        parameter as `edid/<filename>`.
+
+        You can craft one directly here or use sibling options `linuxhw` and `modelines`.
+      '';
+      example = lib.literalExpression ''
+        [
+          (pkgs.runCommand "edid-custom" {} '''
+            mkdir -p "$out/lib/firmware/edid"
+            base64 -d > "$out/lib/firmware/edid/custom1.bin" <<'EOF'
+            <insert your base64 encoded EDID file here `base64 < /sys/class/drm/card0-.../edid`>
+            EOF
+          ''')
+        ]
+      '';
+      apply = list:
+        if list == [ ] then null else
+        (pkgs.buildEnv {
+          name = "firmware-edid";
+          paths = list;
+          pathsToLink = [ "/lib/firmware/edid" ];
+          ignoreCollisions = true;
+        }) // {
+          compressFirmware = false;
+        };
+    };
+
+    hardware.display.edid.linuxhw = lib.mkOption {
+      type = with lib.types; attrsOf (listOf str);
+      default = { };
+      description = ''
+        Exposes EDID files from users-sourced database at https://github.com/linuxhw/EDID
+
+        Attribute names will be mapped to EDID filenames `<NAME>.bin`.
+
+        Attribute values are lists of `awk` regexp patterns that (together) must match
+        exactly one line in either of:
+        - [AnalogDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/AnalogDisplay.md)
+        - [DigitalDisplay.md](https://raw.githubusercontent.com/linuxhw/EDID/master/DigitalDisplay.md)
+
+        There is no universal way of locating your device config, but here are some practical tips:
+        1. locate your device:
+          - find your model number (second column)
+          - locate manufacturer (first column) and go through the list manually
+        2. narrow down results using other columns until there is only one left:
+          - `Name` column
+          - production date (`Made` column)
+          - resolution `Res`
+          - screen diagonal (`Inch` column)
+          - as a last resort use `ID` from the last column
+      '';
+      example = lib.literalExpression ''
+        {
+          PG278Q_2014 = [ "PG278Q" "2014" ];
+        }
+      '';
+      apply = displays:
+        if displays == { } then null else
+        pkgs.linuxhw-edid-fetcher.override { inherit displays; };
+    };
+
+    hardware.display.edid.modelines = lib.mkOption {
+      type = with lib.types; attrsOf str;
+      default = { };
+      description = ''
+        Attribute set of XFree86 Modelines automatically converted
+        and exposed as `edid/<name>.bin` files in initrd.
+        See for more information:
+        - https://en.wikipedia.org/wiki/XFree86_Modeline
+      '';
+      example = lib.literalExpression ''
+        {
+          "PG278Q_60" = "    241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync";
+          "PG278Q_120" = "   497.75   2560 2608 2640 2720   1440 1443 1448 1525   +hsync -vsync";
+          "U2711_60" = "     241.50   2560 2600 2632 2720   1440 1443 1448 1481   -hsync +vsync";
+        }
+      '';
+      apply = modelines:
+        if modelines == { } then null else
+        pkgs.edid-generator.overrideAttrs {
+          clean = true;
+          passthru.config = modelines;
+          modelines = lib.trivial.pipe modelines [
+            (lib.mapAttrsToList (name: value:
+              lib.throwIfNot (builtins.stringLength name <= 12) "Modeline name must be 12 characters or less"
+                ''Modeline "${name}" ${value}''
+            ))
+            (builtins.map (line: "${line}\n"))
+            (lib.strings.concatStringsSep "")
+          ];
+        };
+    };
+
+    hardware.display.outputs = lib.mkOption {
+      type = lib.types.attrsOf (lib.types.submodule ({
+        options = {
+          edid = lib.mkOption {
+            type = with lib.types; nullOr str;
+            default = null;
+            description = ''
+              An EDID filename to be used for configured display, as in `edid/<filename>`.
+              See for more information:
+              - `hardware.display.edid.packages`
+              - https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes_and_EDID
+            '';
+          };
+          mode = lib.mkOption {
+            type = with lib.types; nullOr str;
+            default = null;
+            description = ''
+              A `video` kernel parameter (framebuffer mode) configuration for the specific output:
+
+                  <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
+
+              See for more information:
+              - https://docs.kernel.org/fb/modedb.html
+              - https://wiki.archlinux.org/title/Kernel_mode_setting#Forcing_modes
+            '';
+            example = lib.literalExpression ''
+              "e"
+            '';
+          };
+        };
+      }));
+      description = ''
+        Hardware/kernel-level configuration of specific outputs.
+      '';
+      default = { };
+
+      example = lib.literalExpression ''
+        {
+          edid.modelines."PG278Q_60" = "241.50   2560 2608 2640 2720   1440 1443 1448 1481   -hsync +vsync";
+          outputs."DP-1".edid = "PG278Q_60.bin";
+          outputs."DP-1".mode = "e";
+        }
+      '';
+    };
+  };
+
+  config = lib.mkMerge [
+    {
+      hardware.display.edid.packages =
+        lib.optional (cfg.edid.modelines != null) cfg.edid.modelines
+        ++ lib.optional (cfg.edid.linuxhw != null) cfg.edid.linuxhw;
+
+      boot.kernelParams =
+        # forcing video modes
+        lib.trivial.pipe cfg.outputs [
+          (lib.attrsets.filterAttrs (_: spec: spec.mode != null))
+          (lib.mapAttrsToList (output: spec: "video=${output}:${spec.mode}"))
+        ]
+        ++
+        # selecting EDID for displays
+        lib.trivial.pipe cfg.outputs [
+          (lib.attrsets.filterAttrs (_: spec: spec.edid != null))
+          (lib.mapAttrsToList (output: spec: "${output}:edid/${spec.edid}"))
+          (builtins.concatStringsSep ",")
+          (p: lib.optional (p != "") "drm.edid_firmware=${p}")
+        ]
+      ;
+    }
+    (lib.mkIf (cfg.edid.packages != null) {
+      # services.udev implements hardware.firmware option
+      services.udev.enable = true;
+      hardware.firmware = [ cfg.edid.packages ];
+    })
+  ];
+}
diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix b/nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix
index ca769cc44e5c9..360a832e28cbe 100644
--- a/nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix
+++ b/nixos/modules/services/hardware/nvidia-container-toolkit/cdi-generate.nix
@@ -1,4 +1,5 @@
 {
+  deviceNameStrategy,
   glibc,
   jq,
   lib,
@@ -25,6 +26,7 @@ writeScriptBin "nvidia-cdi-generator"
 function cdiGenerate {
   ${lib.getExe' nvidia-container-toolkit "nvidia-ctk"} cdi generate \
     --format json \
+    --device-name-strategy ${deviceNameStrategy} \
     --ldconfig-path ${lib.getExe' glibc "ldconfig"} \
     --library-search-path ${lib.getLib nvidia-driver}/lib \
     --nvidia-ctk-path ${lib.getExe' nvidia-container-toolkit "nvidia-ctk"}
diff --git a/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix b/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix
index bd12667a56474..fe4999bfbdbbe 100644
--- a/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix
+++ b/nixos/modules/services/hardware/nvidia-container-toolkit/default.nix
@@ -52,6 +52,17 @@
         '';
       };
 
+      device-name-strategy = lib.mkOption {
+        default = "index";
+        type = lib.types.enum [ "index" "uuid" "type-index" ];
+        description = ''
+          Specify the strategy for generating device names,
+          passed to `nvidia-ctk cdi generate`. This will affect how
+          you reference the device using `nvidia.com/gpu=` in
+          the container runtime.
+        '';
+      };
+
       mount-nvidia-docker-1-directories = lib.mkOption {
         default = true;
         type = lib.types.bool;
@@ -119,6 +130,7 @@
             script = pkgs.callPackage ./cdi-generate.nix {
               inherit (config.hardware.nvidia-container-toolkit) mounts;
               nvidia-driver = config.hardware.nvidia.package;
+              deviceNameStrategy = config.hardware.nvidia-container-toolkit.device-name-strategy;
             };
           in
           lib.getExe script;
diff --git a/nixos/modules/services/hardware/openrgb.nix b/nixos/modules/services/hardware/openrgb.nix
index 1f7e4ffb9265a..4dc521aa97800 100644
--- a/nixos/modules/services/hardware/openrgb.nix
+++ b/nixos/modules/services/hardware/openrgb.nix
@@ -51,5 +51,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix
index 5f166bb320baf..b6b36b6b60f46 100644
--- a/nixos/modules/services/hardware/sane.nix
+++ b/nixos/modules/services/hardware/sane.nix
@@ -177,6 +177,10 @@ in
 
       users.groups.scanner.gid = config.ids.gids.scanner;
       networking.firewall.allowedUDPPorts = mkIf config.hardware.sane.openFirewall [ 8612 ];
+
+      systemd.tmpfiles.rules = [
+        "d /var/lock/sane 0770 root scanner - -"
+      ];
     })
 
     (mkIf config.services.saned.enable {
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 62603d20e2d30..67956fdd6c763 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -29,9 +29,6 @@ let
   };
 
   nixosRules = ''
-    # Miscellaneous devices.
-    KERNEL=="kvm",                  MODE="0666"
-
     # Needed for gpm.
     SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
   '';
@@ -93,7 +90,7 @@ let
       echo "OK"
 
       echo -n "Checking that all programs called by absolute paths in udev rules exist... "
-      import_progs=$(grep 'IMPORT{program}="\/' $out/* |
+      import_progs=$(grep 'IMPORT{program}="/' $out/* |
         sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
       run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' |
         sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
diff --git a/nixos/modules/services/home-automation/home-assistant.nix b/nixos/modules/services/home-automation/home-assistant.nix
index c58a31539ed81..6ffe314d45747 100644
--- a/nixos/modules/services/home-automation/home-assistant.nix
+++ b/nixos/modules/services/home-automation/home-assistant.nix
@@ -192,7 +192,12 @@ in {
     };
 
     customComponents = mkOption {
-      type = types.listOf types.package;
+      type = types.listOf (
+        types.addCheck types.package (p: p.isHomeAssistantComponent or false) // {
+          name = "home-assistant-component";
+          description = "package that is a Home Assistant component";
+        }
+      );
       default = [];
       example = literalExpression ''
         with pkgs.home-assistant-custom-components; [
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index 25982022c0684..caeac16815f42 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -15,6 +15,7 @@ let
     message_journal_dir = ${cfg.messageJournalDir}
     mongodb_uri = ${cfg.mongodbUri}
     plugin_dir = /var/lib/graylog/plugins
+    data_dir = ${cfg.dataDir}
 
     ${cfg.extraConfig}
   '';
@@ -93,6 +94,12 @@ in
         description = "List of valid URIs of the http ports of your elastic nodes. If one or more of your elasticsearch hosts require authentication, include the credentials in each node URI that requires authentication";
       };
 
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/graylog/data";
+        description = "Directory used to store Graylog server state.";
+      };
+
       messageJournalDir = mkOption {
         type = types.str;
         default = "/var/lib/graylog/data/journal";
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index b2a6b3ab6784b..1d17dd66e0716 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -328,7 +328,7 @@ in
 
     createMailUser = mkEnableOption ''automatically creating the user
       given in {option}`services.dovecot.user` and the group
-      given in {option}`services.dovecot.group`.'' // { default = true; };
+      given in {option}`services.dovecot.group`'' // { default = true; };
 
     modules = mkOption {
       type = types.listOf types.package;
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
index ab10206fea42e..b55a7422702fc 100644
--- a/nixos/modules/services/mail/mailman.nix
+++ b/nixos/modules/services/mail/mailman.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:          # mailman.nix
+{ config, pkgs, lib, ... }:
 
 with lib;
 
@@ -367,7 +367,7 @@ in {
           for more info.
         '';
       }
-      (requirePostfixHash [ "relayDomains" ] "postfix_domains")
+      (requirePostfixHash [ "config" "relay_domains" ] "postfix_domains")
       (requirePostfixHash [ "config" "transport_maps" ] "postfix_lmtp")
       (requirePostfixHash [ "config" "local_recipient_maps" ] "postfix_lmtp")
     ]);
@@ -445,6 +445,7 @@ in {
           "${removeSuffix "/" cfg.serve.virtualRoot}/static/".alias = webSettings.STATIC_ROOT + "/";
         };
       });
+      proxyTimeout = mkDefault "120s";
     };
 
     environment.systemPackages = [ (pkgs.buildEnv {
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index fd78c98d0cb41..6bb501959f95d 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -484,7 +484,7 @@ in
       };
 
       config = mkOption {
-        type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
+        type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
         description = ''
           The main.cf configuration file as key value set.
         '';
diff --git a/nixos/modules/services/mail/protonmail-bridge.nix b/nixos/modules/services/mail/protonmail-bridge.nix
new file mode 100644
index 0000000000000..da58d9731f049
--- /dev/null
+++ b/nixos/modules/services/mail/protonmail-bridge.nix
@@ -0,0 +1,60 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.protonmail-bridge;
+in
+{
+  options.services.protonmail-bridge = {
+    enable = lib.mkEnableOption "protonmail bridge";
+
+    package = lib.mkPackageOption pkgs "protonmail-bridge" { };
+
+    path = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      example = lib.literalExpression "with pkgs; [ pass gnome-keyring ]";
+      description = "List of derivations to put in protonmail-bride's path.";
+    };
+
+    logLevel = lib.mkOption {
+      type = lib.types.nullOr (
+        lib.types.enum [
+          "panic"
+          "fatal"
+          "error"
+          "warn"
+          "info"
+          "debug"
+        ]
+      );
+      default = null;
+      description = "Log level of the Proton Mail Bridge service. If set to null then the service uses it's default log level.";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.user.services.protonmail-bridge = {
+      description = "protonmail bridge";
+      wantedBy = [ "graphical-session.target" ];
+      after = [ "graphical-session.target" ];
+
+      serviceConfig =
+        let
+          logLevel = lib.optionalString (cfg.logLevel != null) "--log-level ${cfg.logLevel}";
+        in
+        {
+          ExecStart = "${lib.getExe cfg.package} --noninteractive ${logLevel}";
+          Restart = "always";
+        };
+
+      path = cfg.path;
+    };
+    environment.systemPackages = [ cfg.package ];
+  };
+  meta.maintainers = with lib.maintainers; [ mzacho ];
+}
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index 78f627d33e2df..2914877bdccde 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -93,13 +93,17 @@ in
     maxAttachmentSize = mkOption {
       type = types.int;
       default = 18;
+      apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.37)}M";
       description = ''
         The maximum attachment size in MB.
-
-        Note: Since roundcube only uses 70% of max upload values configured in php
-        30% is added automatically to [](#opt-services.roundcube.maxAttachmentSize).
+        [upstream issue comment]: https://github.com/roundcube/roundcubemail/issues/7979#issuecomment-808879209
+        ::: {.note}
+        Since there is some overhead in base64 encoding applied to attachments, + 37% will be added
+        to the value set in this option in order to offset the overhead. For example, setting
+        `maxAttachmentSize` to `100` would result in `137M` being the real value in the configuration.
+        See [upstream issue comment] for more details on the motivations behind this.
+        :::
       '';
-      apply = configuredMaxAttachmentSize: "${toString (configuredMaxAttachmentSize * 1.3)}M";
     };
 
     configureNginx = lib.mkOption {
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index 6095268eb9608..d980069608e79 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -141,7 +141,7 @@ in {
           -Dairsonic.home=${cfg.home} \
           -Dserver.address=${cfg.listenAddress} \
           -Dserver.port=${toString cfg.port} \
-          -Dairsonic.contextPath=${cfg.contextPath} \
+          -Dserver.context-path=${cfg.contextPath} \
           -Djava.awt.headless=true \
           ${optionalString (cfg.virtualHost != null)
             "-Dserver.use-forward-headers=true"} \
diff --git a/nixos/modules/services/misc/ananicy.nix b/nixos/modules/services/misc/ananicy.nix
index f7ab41fcce61d..2129868a2919b 100644
--- a/nixos/modules/services/misc/ananicy.nix
+++ b/nixos/modules/services/misc/ananicy.nix
@@ -1,85 +1,119 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 let
   cfg = config.services.ananicy;
-  configFile = pkgs.writeText "ananicy.conf" (generators.toKeyValue { } cfg.settings);
-  extraRules = pkgs.writeText "extraRules" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraRules);
-  extraTypes = pkgs.writeText "extraTypes" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraTypes);
-  extraCgroups = pkgs.writeText "extraCgroups" (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraCgroups);
-  servicename = if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then "ananicy-cpp" else "ananicy";
+  configFile = pkgs.writeText "ananicy.conf" (lib.generators.toKeyValue { } cfg.settings);
+  extraRules = pkgs.writeText "extraRules" (
+    lib.concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraRules
+  );
+  extraTypes = pkgs.writeText "extraTypes" (
+    lib.concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraTypes
+  );
+  extraCgroups = pkgs.writeText "extraCgroups" (
+    lib.concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.extraCgroups
+  );
+  servicename =
+    if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then "ananicy-cpp" else "ananicy";
+  # Ananicy-CPP with BPF is not supported on hardened kernels https://github.com/NixOS/nixpkgs/issues/327382
+  finalPackage =
+    if (servicename == "ananicy-cpp" && config.boot.kernelPackages.isHardened) then
+      (cfg.package { withBpf = false; })
+    else
+      cfg.package;
 in
 {
-  options = {
-    services.ananicy = {
-      enable = mkEnableOption "Ananicy, an auto nice daemon";
+  options.services.ananicy = {
+    enable = lib.mkEnableOption "Ananicy, an auto nice daemon";
 
-      package = mkPackageOption pkgs "ananicy" {
-        example = "ananicy-cpp";
-      };
+    package = lib.mkPackageOption pkgs "ananicy" { example = "ananicy-cpp"; };
 
-      rulesProvider = mkPackageOption pkgs "ananicy" {
-        example = "ananicy-cpp";
-      } // {
-        description = ''
-          Which package to copy default rules,types,cgroups from.
-        '';
-      };
+    rulesProvider = lib.mkPackageOption pkgs "ananicy" { example = "ananicy-cpp"; } // {
+      description = ''
+        Which package to copy default rules,types,cgroups from.
+      '';
+    };
 
-      settings = mkOption {
-        type = with types; attrsOf (oneOf [ int bool str ]);
-        default = { };
-        example = {
-          apply_nice = false;
-        };
-        description = ''
-          See <https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf>
-        '';
+    settings = lib.mkOption {
+      type =
+        with lib.types;
+        attrsOf (oneOf [
+          int
+          bool
+          str
+        ]);
+      default = { };
+      example = {
+        apply_nice = false;
       };
+      description = ''
+        See <https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf>
+      '';
+    };
 
-      extraRules = mkOption {
-        type = with types; listOf attrs;
-        default = [ ];
-        description = ''
-          Rules to write in 'nixRules.rules'. See:
-          <https://github.com/Nefelim4ag/Ananicy#configuration>
-          <https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration>
-        '';
-        example = [
-          { name = "eog"; type = "Image-Viewer"; }
-          { name = "fdupes"; type = "BG_CPUIO"; }
-        ];
-      };
-      extraTypes = mkOption {
-        type = with types; listOf attrs;
-        default = [ ];
-        description = ''
-          Types to write in 'nixTypes.types'. See:
-          <https://gitlab.com/ananicy-cpp/ananicy-cpp/#types>
-        '';
-        example = [
-          { type = "my_type"; nice = 19; other_parameter = "value"; }
-          { type = "compiler"; nice = 19; sched = "batch"; ioclass = "idle"; }
-        ];
-      };
-      extraCgroups = mkOption {
-        type = with types; listOf attrs;
-        default = [ ];
-        description = ''
-          Cgroups to write in 'nixCgroups.cgroups'. See:
-          <https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups>
-        '';
-        example = [
-          { cgroup = "cpu80"; CPUQuota = 80; }
-        ];
-      };
+    extraRules = lib.mkOption {
+      type = with lib.types; listOf attrs;
+      default = [ ];
+      description = ''
+        Rules to write in 'nixRules.rules'. See:
+        <https://github.com/Nefelim4ag/Ananicy#configuration>
+        <https://gitlab.com/ananicy-cpp/ananicy-cpp/#global-configuration>
+      '';
+      example = [
+        {
+          name = "eog";
+          type = "Image-Viewer";
+        }
+        {
+          name = "fdupes";
+          type = "BG_CPUIO";
+        }
+      ];
+    };
+    extraTypes = lib.mkOption {
+      type = with lib.types; listOf attrs;
+      default = [ ];
+      description = ''
+        Types to write in 'nixTypes.types'. See:
+        <https://gitlab.com/ananicy-cpp/ananicy-cpp/#types>
+      '';
+      example = [
+        {
+          type = "my_type";
+          nice = 19;
+          other_parameter = "value";
+        }
+        {
+          type = "compiler";
+          nice = 19;
+          sched = "batch";
+          ioclass = "idle";
+        }
+      ];
+    };
+    extraCgroups = lib.mkOption {
+      type = with lib.types; listOf attrs;
+      default = [ ];
+      description = ''
+        Cgroups to write in 'nixCgroups.cgroups'. See:
+        <https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups>
+      '';
+      example = [
+        {
+          cgroup = "cpu80";
+          CPUQuota = 80;
+        }
+      ];
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     environment = {
-      systemPackages = [ cfg.package ];
+      systemPackages = [ finalPackage ];
       etc."ananicy.d".source = pkgs.runCommandLocal "ananicyfiles" { } ''
         mkdir -p $out
         # ananicy-cpp does not include rules or settings on purpose
@@ -92,16 +126,16 @@ in
         # configured through .setings
         rm -f $out/ananicy.conf
         cp ${configFile} $out/ananicy.conf
-        ${optionalString (cfg.extraRules != [ ]) "cp ${extraRules} $out/nixRules.rules"}
-        ${optionalString (cfg.extraTypes != [ ]) "cp ${extraTypes} $out/nixTypes.types"}
-        ${optionalString (cfg.extraCgroups != [ ]) "cp ${extraCgroups} $out/nixCgroups.cgroups"}
+        ${lib.optionalString (cfg.extraRules != [ ]) "cp ${extraRules} $out/nixRules.rules"}
+        ${lib.optionalString (cfg.extraTypes != [ ]) "cp ${extraTypes} $out/nixTypes.types"}
+        ${lib.optionalString (cfg.extraCgroups != [ ]) "cp ${extraCgroups} $out/nixCgroups.cgroups"}
       '';
     };
 
     # ananicy and ananicy-cpp have different default settings
     services.ananicy.settings =
       let
-        mkOD = mkOptionDefault;
+        mkOD = lib.mkOptionDefault;
       in
       {
         cgroup_load = mkOD true;
@@ -113,28 +147,30 @@ in
         apply_sched = mkOD true;
         apply_oom_score_adj = mkOD true;
         apply_cgroup = mkOD true;
-      } // (if ((lib.getName cfg.package) == (lib.getName pkgs.ananicy-cpp)) then {
-        # https://gitlab.com/ananicy-cpp/ananicy-cpp/-/blob/master/src/config.cpp#L12
-        loglevel = mkOD "warn"; # default is info but its spammy
-        cgroup_realtime_workaround = mkOD config.systemd.enableUnifiedCgroupHierarchy;
-        log_applied_rule = mkOD false;
-      } else {
-        # https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf
-        check_disks_schedulers = mkOD true;
-        check_freq = mkOD 5;
-      });
+      }
+      // (
+        if servicename == "ananicy-cpp" then
+          {
+            # https://gitlab.com/ananicy-cpp/ananicy-cpp/-/blob/master/src/config.cpp#L12
+            loglevel = mkOD "warn"; # default is info but its spammy
+            cgroup_realtime_workaround = true;
+            log_applied_rule = mkOD false;
+          }
+        else
+          {
+            # https://github.com/Nefelim4ag/Ananicy/blob/master/ananicy.d/ananicy.conf
+            check_disks_schedulers = mkOD true;
+            check_freq = mkOD 5;
+          }
+      );
 
     systemd = {
-      # https://gitlab.com/ananicy-cpp/ananicy-cpp/#cgroups applies to both ananicy and -cpp
-      enableUnifiedCgroupHierarchy = mkDefault false;
-      packages = [ cfg.package ];
+      packages = [ finalPackage ];
       services."${servicename}" = {
         wantedBy = [ "default.target" ];
       };
     };
   };
 
-  meta = {
-    maintainers = with maintainers; [ artturin ];
-  };
+  meta.maintainers = with lib.maintainers; [ artturin ];
 }
diff --git a/nixos/modules/services/misc/blenderfarm.nix b/nixos/modules/services/misc/blenderfarm.nix
new file mode 100644
index 0000000000000..0d8ecf7af8e20
--- /dev/null
+++ b/nixos/modules/services/misc/blenderfarm.nix
@@ -0,0 +1,141 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  cfg = config.services.blendfarm;
+  json = pkgs.formats.json { };
+  configFile = json.generate "ServerSettings" (defaultConfig // cfg.serverConfig);
+  defaultConfig = {
+    Port = 15000;
+    BroadcastPort = 16342;
+    BypassScriptUpdate = false;
+    BasicSecurityPassword = null;
+  };
+in
+{
+  meta.maintainers = with lib.maintainers; [ gador ];
+
+  options.services.blendfarm = with lib.types; {
+    enable = lib.mkEnableOption "Blendfarm, a render farm management software for Blender";
+    package = lib.mkPackageOption pkgs "blendfarm" { };
+    openFirewall = lib.mkEnableOption "allowing blendfarm network access through the firewall";
+
+    user = lib.mkOption {
+      description = "User under which blendfarm runs.";
+      default = "blendfarm";
+      type = str;
+    };
+
+    group = lib.mkOption {
+      description = "Group under which blendfarm runs.";
+      default = "blendfarm";
+      type = str;
+    };
+
+    basicSecurityPasswordFile = lib.mkOption {
+      description = ''Path to the password file the client needs to connect to the server.
+      The password must not contain a forward slash.'';
+      default = null;
+      type = nullOr str;
+    };
+
+    blenderPackage = lib.mkPackageOption pkgs "blender" { };
+
+    serverConfig = lib.mkOption {
+      description = "Server configuration";
+      default = defaultConfig;
+      type = submodule {
+        freeformType = attrsOf anything;
+        options = {
+          Port = lib.mkOption {
+            description = "Default port blendfarm server listens on.";
+            default = 15000;
+            type = types.port;
+          };
+          BroadcastPort = lib.mkOption {
+            description = "Default port blendfarm server advertises itself on.";
+            default = 16342;
+            type = types.port;
+          };
+
+          BypassScriptUpdate = lib.mkOption {
+            description = "Prevents blendfarm from replacing the .py self-generated scripts.";
+            default = false;
+            type = bool;
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    networking.firewall = lib.optionalAttrs (cfg.openFirewall) {
+      allowedTCPPorts = [ cfg.serverConfig.Port ];
+      allowedUDPPorts = [ cfg.serverConfig.BroadcastPort ];
+    };
+
+    systemd.services.blendfarm-server = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      description = "blendfarm server";
+      path = [ cfg.blenderPackage ];
+      preStart = ''
+        rm -f ServerSettings
+        install -m640 ${configFile} ServerSettings
+        if [ ! -d "BlenderData/nix-blender-linux64" ]; then
+          mkdir -p BlenderData/nix-blender-linux64
+          echo "nix-blender" > VersionCustom
+        fi
+        rm -f BlenderData/nix-blender-linux64/blender
+        ln -s ${lib.getExe cfg.blenderPackage} BlenderData/nix-blender-linux64/blender
+      '' +
+      lib.optionalString (cfg.basicSecurityPasswordFile != null) ''
+        BLENDFARM_PASSWORD=$(${pkgs.systemd}/bin/systemd-creds cat BLENDFARM_PASS_FILE)
+        sed -i "s/null/\"$BLENDFARM_PASSWORD\"/g" ServerSettings
+      '';
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/LogicReinc.BlendFarm.Server";
+        DynamicUser = true;
+        LogsDirectory = "blendfarm";
+        StateDirectory = "blendfarm";
+        WorkingDirectory = "/var/lib/blendfarm";
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectoryMode = "0755";
+        LoadCredential = lib.optional (cfg.basicSecurityPasswordFile != null) "BLENDFARM_PASS_FILE:${cfg.basicSecurityPasswordFile}";
+        ReadWritePaths = "";
+        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"
+          "@chown"
+        ];
+        RestrictRealtime = true;
+        LockPersonality = true;
+        UMask = "0066";
+        ProtectHostname = true;
+      };
+    };
+
+    users.users.blendfarm = {
+      isSystemUser = true;
+      group = "blendfarm";
+    };
+    users.groups.blendfarm = { };
+  };
+}
diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix
index 8cb51bb0b7a7f..6660d5e977ffb 100644
--- a/nixos/modules/services/misc/dictd.nix
+++ b/nixos/modules/services/misc/dictd.nix
@@ -62,6 +62,9 @@ in
       description = "DICT.org Dictionary Server";
       wantedBy = [ "multi-user.target" ];
       environment = { LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; };
+      # Work around the fact that dictd doesn't handle SIGTERM; it terminates
+      # with code 143 instead of exiting with code 0.
+      serviceConfig.SuccessExitStatus = [ 143 ];
       serviceConfig.Type = "forking";
       script = "${pkgs.dict}/sbin/dictd -s -c ${dictdb}/share/dictd/dictd.conf --locale en_US.UTF-8";
     };
diff --git a/nixos/modules/services/misc/etebase-server.nix b/nixos/modules/services/misc/etebase-server.nix
index 7b6b5249f230c..9b2ba34cc30ba 100644
--- a/nixos/modules/services/misc/etebase-server.nix
+++ b/nixos/modules/services/misc/etebase-server.nix
@@ -45,7 +45,7 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.python3.pkgs.etebase-server;
+        default = pkgs.etebase-server;
         defaultText = literalExpression "pkgs.python3.pkgs.etebase-server";
         description = "etebase-server package to use.";
       };
diff --git a/nixos/modules/services/misc/flaresolverr.nix b/nixos/modules/services/misc/flaresolverr.nix
new file mode 100644
index 0000000000000..7967580307f99
--- /dev/null
+++ b/nixos/modules/services/misc/flaresolverr.nix
@@ -0,0 +1,58 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+
+let
+  cfg = config.services.flaresolverr;
+in
+{
+  options = {
+    services.flaresolverr = {
+      enable = lib.mkEnableOption "FlareSolverr, a proxy server to bypass Cloudflare protection";
+
+      package = lib.mkPackageOption pkgs "flaresolverr" { };
+
+      openFirewall = lib.mkOption {
+        type = lib.types.bool;
+        default = false;
+        description = "Open the port in the firewall for FlareSolverr.";
+      };
+
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 8191;
+        description = "The port on which FlareSolverr will listen for incoming HTTP traffic.";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.flaresolverr = {
+      description = "FlareSolverr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        HOME = "/run/flaresolverr";
+        PORT = toString cfg.port;
+      };
+
+      serviceConfig = {
+        SyslogIdentifier = "flaresolverr";
+        Restart = "always";
+        RestartSec = 5;
+        Type = "simple";
+        DynamicUser = true;
+        RuntimeDirectory = "flaresolverr";
+        WorkingDirectory = "/run/flaresolverr";
+        ExecStart = lib.getExe cfg.package;
+        TimeoutStopSec = 30;
+      };
+    };
+
+    networking.firewall = lib.mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; };
+  };
+}
diff --git a/nixos/modules/services/misc/forgejo.nix b/nixos/modules/services/misc/forgejo.nix
index 9a102918f35e3..9aa7b13b02e19 100644
--- a/nixos/modules/services/misc/forgejo.nix
+++ b/nixos/modules/services/misc/forgejo.nix
@@ -66,7 +66,7 @@ in
     services.forgejo = {
       enable = mkEnableOption "Forgejo, a software forge";
 
-      package = mkPackageOption pkgs "forgejo" { };
+      package = mkPackageOption pkgs "forgejo-lts" { };
 
       useWizard = mkOption {
         default = false;
diff --git a/nixos/modules/services/misc/fstrim.nix b/nixos/modules/services/misc/fstrim.nix
index d2dda2636ef1b..10dced2c4e64b 100644
--- a/nixos/modules/services/misc/fstrim.nix
+++ b/nixos/modules/services/misc/fstrim.nix
@@ -41,5 +41,5 @@ in {
 
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 492c669f180a9..9fd6014f2c71c 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -9,6 +9,8 @@ let
   toml = pkgs.formats.toml {};
   yaml = pkgs.formats.yaml {};
 
+  git = cfg.packages.gitaly.git;
+
   postgresqlPackage = if config.services.postgresql.enable then
                         config.services.postgresql.package
                       else
@@ -51,7 +53,7 @@ let
     prometheus_listen_addr = "localhost:9236"
 
     [git]
-    bin_path = "${pkgs.git}/bin/git"
+    bin_path = "${git}/bin/git"
 
     [gitlab-shell]
     dir = "${cfg.packages.gitlab-shell}"
@@ -184,16 +186,15 @@ let
     MALLOC_ARENA_MAX = "2";
   } // cfg.extraEnv;
 
-  runtimeDeps = with pkgs; [
+  runtimeDeps = [ git ] ++ (with pkgs; [
     nodejs
     gzip
-    git
     gnutar
     postgresqlPackage
     coreutils
     procps
     findutils # Needed for gitlab:cleanup:orphan_job_artifact_files
-  ];
+  ]);
 
   gitlab-rake = pkgs.stdenv.mkDerivation {
     name = "gitlab-rake";
@@ -1124,7 +1125,7 @@ in {
       }
     ];
 
-    environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
+    environment.systemPackages = [ gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
 
     systemd.targets.gitlab = {
       description = "Common target for all GitLab services.";
@@ -1295,12 +1296,11 @@ in {
     systemd.services.gitlab-config = {
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         jq
         openssl
         replace-secret
-        git
-      ];
+      ]);
       serviceConfig = {
         Type = "oneshot";
         User = cfg.user;
@@ -1338,7 +1338,7 @@ in {
           ln -sf ${cableYml} ${cfg.statePath}/config/cable.yml
           ln -sf ${resqueYml} ${cfg.statePath}/config/resque.yml
 
-          ${cfg.packages.gitlab-shell}/bin/install
+          ${cfg.packages.gitlab-shell}/bin/gitlab-shell-install
 
           ${optionalString cfg.smtp.enable ''
               install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
@@ -1458,9 +1458,8 @@ in {
         SIDEKIQ_MEMORY_KILLER_GRACE_TIME = cfg.sidekiq.memoryKiller.graceTime;
         SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT = cfg.sidekiq.memoryKiller.shutdownWait;
       });
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         postgresqlPackage
-        git
         ruby
         openssh
         nodejs
@@ -1473,7 +1472,7 @@ in {
         gzip
 
         procps # Sidekiq MemoryKiller
-      ];
+      ]);
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -1500,12 +1499,11 @@ in {
       bindsTo = [ "gitlab-config.service" ];
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         openssh
-        git
         gzip
         bzip2
-      ];
+      ]);
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -1582,7 +1580,7 @@ in {
       after = [ "network.target" ];
       wantedBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         remarshal
         exiftool
         git
@@ -1590,7 +1588,7 @@ in {
         gzip
         openssh
         cfg.packages.gitlab-workhorse
-      ];
+      ]);
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -1658,15 +1656,14 @@ in {
       requiredBy = [ "gitlab.target" ];
       partOf = [ "gitlab.target" ];
       environment = gitlabEnv;
-      path = with pkgs; [
+      path = [ git ] ++ (with pkgs; [
         postgresqlPackage
-        git
         openssh
         nodejs
         procps
         gnupg
         gzip
-      ];
+      ]);
       serviceConfig = {
         Type = "notify";
         User = cfg.user;
diff --git a/nixos/modules/services/misc/gitweb.nix b/nixos/modules/services/misc/gitweb.nix
index ec08ab51a4574..8f4869ce5d559 100644
--- a/nixos/modules/services/misc/gitweb.nix
+++ b/nixos/modules/services/misc/gitweb.nix
@@ -55,6 +55,6 @@ in
 
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 
 }
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index f320e78a91060..fb9b9e19813f1 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -1,4 +1,9 @@
-{ config, lib, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 with lib;
 
@@ -7,6 +12,17 @@ let
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule
+      [
+        "services"
+        "gollum"
+        "mathjax"
+      ]
+      "MathJax rendering might be discontinued in the future, use services.gollum.math instead to enable KaTeX rendering or file a PR if you really need Mathjax"
+    )
+  ];
+
   options.services.gollum = {
     enable = mkEnableOption "Gollum, a git-powered wiki service";
 
@@ -28,20 +44,30 @@ in
       description = "Content of the configuration file";
     };
 
-    mathjax = mkOption {
+    math = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable support for math rendering using MathJax";
+      description = "Enable support for math rendering using KaTeX";
     };
 
     allowUploads = mkOption {
-      type = types.nullOr (types.enum [ "dir" "page" ]);
+      type = types.nullOr (
+        types.enum [
+          "dir"
+          "page"
+        ]
+      );
       default = null;
       description = "Enable uploads of external files";
     };
 
     user-icons = mkOption {
-      type = types.nullOr (types.enum [ "gravatar" "identicon" ]);
+      type = types.nullOr (
+        types.enum [
+          "gravatar"
+          "identicon"
+        ]
+      );
       default = null;
       description = "Enable specific user icons for history view";
     };
@@ -109,9 +135,7 @@ in
 
     users.groups."${cfg.group}" = { };
 
-    systemd.tmpfiles.rules = [
-      "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
-    ];
+    systemd.tmpfiles.rules = [ "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" ];
 
     systemd.services.gollum = {
       description = "Gollum wiki";
@@ -134,7 +158,7 @@ in
             --host ${cfg.address} \
             --config ${pkgs.writeText "gollum-config.rb" cfg.extraConfig} \
             --ref ${cfg.branch} \
-            ${optionalString cfg.mathjax "--mathjax"} \
+            ${optionalString cfg.math "--math"} \
             ${optionalString cfg.emoji "--emoji"} \
             ${optionalString cfg.h1-title "--h1-title"} \
             ${optionalString cfg.no-edit "--no-edit"} \
@@ -147,5 +171,8 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ erictapen bbenno ];
+  meta.maintainers = with lib.maintainers; [
+    erictapen
+    bbenno
+  ];
 }
diff --git a/nixos/modules/services/misc/gotenberg.nix b/nixos/modules/services/misc/gotenberg.nix
new file mode 100644
index 0000000000000..57932c656d632
--- /dev/null
+++ b/nixos/modules/services/misc/gotenberg.nix
@@ -0,0 +1,258 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.gotenberg;
+
+  args =
+    [
+      "--api-port=${toString cfg.port}"
+      "--api-timeout=${cfg.timeout}"
+      "--api-root-path=${cfg.rootPath}"
+      "--log-level=${cfg.logLevel}"
+      "--chromium-max-queue-size=${toString cfg.chromium.maxQueueSize}"
+      "--libreoffice-restart-after=${toString cfg.libreoffice.restartAfter}"
+      "--libreoffice-max-queue-size=${toString cfg.libreoffice.maxQueueSize}"
+      "--pdfengines-engines=${lib.concatStringsSep "," cfg.pdfEngines}"
+    ]
+    ++ optional cfg.enableBasicAuth "--api-enable-basic-auth"
+    ++ optional cfg.chromium.autoStart "--chromium-auto-start"
+    ++ optional cfg.chromium.disableJavascript "--chromium-disable-javascript"
+    ++ optional cfg.chromium.disableRoutes "--chromium-disable-routes"
+    ++ optional cfg.libreoffice.autoStart "--libreoffice-auto-start"
+    ++ optional cfg.libreoffice.disableRoutes "--libreoffice-disable-routes";
+
+  inherit (lib)
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    types
+    mkIf
+    optional
+    optionalAttrs
+    ;
+in
+{
+  options = {
+    services.gotenberg = {
+      enable = mkEnableOption "Gotenberg, a stateless API for PDF files";
+
+      # Users can override only gotenberg, libreoffice and chromium if they want to (eg. ungoogled-chromium, different LO version, etc)
+      # Don't allow setting the qpdf, pdftk, or unoconv paths, as those are very stable
+      # and there's only one version of each.
+      package = mkPackageOption pkgs "gotenberg" { };
+
+      port = mkOption {
+        type = types.port;
+        default = 3000;
+        description = "Port on which the API should listen.";
+      };
+
+      timeout = mkOption {
+        type = types.nullOr types.str;
+        default = "30s";
+        description = "Timeout for API requests.";
+      };
+
+      rootPath = mkOption {
+        type = types.str;
+        default = "/";
+        description = "Root path for the Gotenberg API.";
+      };
+
+      enableBasicAuth = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          HTTP Basic Authentication.
+
+          If you set this, be sure to set `GOTENBERG_API_BASIC_AUTH_USERNAME`and `GOTENBERG_API_BASIC_AUTH_PASSWORD`
+          in your `services.gotenberg.environmentFile` file.
+        '';
+      };
+
+      extraFontPackages = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        description = "Extra fonts to make available.";
+      };
+
+      chromium = {
+        package = mkPackageOption pkgs "chromium" { };
+
+        maxQueueSize = mkOption {
+          type = types.int;
+          default = 0;
+          description = "Maximum queue size for chromium-based conversions. Setting to 0 disables the limit.";
+        };
+
+        autoStart = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Automatically start chromium when Gotenberg starts. If false, Chromium will start on the first conversion request that uses it.";
+        };
+
+        disableJavascript = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Disable Javascript execution.";
+        };
+
+        disableRoutes = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Disable all routes allowing Chromium-based conversion.";
+        };
+      };
+
+      libreoffice = {
+        package = mkPackageOption pkgs "libreoffice" { };
+
+        restartAfter = mkOption {
+          type = types.int;
+          default = 10;
+          description = "Restart LibreOffice after this many conversions. Setting to 0 disables this feature.";
+        };
+
+        maxQueueSize = mkOption {
+          type = types.int;
+          default = 0;
+          description = "Maximum queue size for LibreOffice-based conversions. Setting to 0 disables the limit.";
+        };
+
+        autoStart = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Automatically start LibreOffice when Gotenberg starts. If false, Chromium will start on the first conversion request that uses it.";
+        };
+
+        disableRoutes = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Disable all routes allowing LibreOffice-based conversion.";
+        };
+      };
+
+      pdfEngines = mkOption {
+        type = types.listOf (
+          types.enum [
+            "pdftk"
+            "qpdf"
+            "libreoffice-pdfengine"
+            "exiftool"
+            "pdfcpu"
+          ]
+        );
+        default = [
+          "pdftk"
+          "qpdf"
+          "libreoffice-pdfengine"
+          "exiftool"
+          "pdfcpu"
+        ];
+        description = ''
+          PDF engines to enable. Each one can be used to perform a specific task.
+          See [the documentation](https://gotenberg.dev/docs/configuration#pdf-engines) for more details.
+          Defaults to all possible PDF engines.
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.enum [
+          "error"
+          "warn"
+          "info"
+          "debug"
+        ];
+        default = "info";
+        description = "The logging level for Gotenberg.";
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Environment file to load extra environment variables from.";
+      };
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = "Any extra command-line flags to pass to the Gotenberg service.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.enableBasicAuth -> cfg.environmentFile != null;
+        message = ''
+          When enabling HTTP Basic Authentication with `services.gotenberg.enableBasicAuth`,
+          you must provide an environment file via `services.gotenberg.environmentFile` with the appropriate environment variables set in it.
+
+          See `services.gotenberg.enableBasicAuth` for the names of those variables.
+        '';
+      }
+    ];
+
+    systemd.services.gotenberg = {
+      description = "Gotenberg API server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ cfg.package ];
+      environment = {
+        LIBREOFFICE_BIN_PATH = "${cfg.libreoffice.package}/lib/libreoffice/program/soffice.bin";
+        CHROMIUM_BIN_PATH = lib.getExe cfg.chromium.package;
+        FONTCONFIG_FILE = pkgs.makeFontsConf {
+          fontDirectories = [ pkgs.liberation_ttf_v2 ] ++ cfg.extraFontPackages;
+        };
+      };
+      serviceConfig = {
+        Type = "simple";
+        DynamicUser = true;
+        ExecStart = "${lib.getExe cfg.package} ${lib.escapeShellArgs args}";
+
+        # Hardening options
+        PrivateDevices = true;
+        PrivateIPC = true;
+        PrivateUsers = true;
+
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProcSubset = "pid";
+
+        RestrictAddressFamilies = [
+          "AF_UNIX"
+          "AF_INET"
+          "AF_INET6"
+          "AF_NETLINK"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+
+        SystemCallFilter = [
+          "@system-service"
+          "~@resources"
+          "~@privileged"
+        ];
+        SystemCallArchitectures = "native";
+
+        UMask = 77;
+      } // optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ pyrox0 ];
+}
diff --git a/nixos/modules/services/misc/graphical-desktop.nix b/nixos/modules/services/misc/graphical-desktop.nix
index c8fe0d921c6ad..246310195edc2 100644
--- a/nixos/modules/services/misc/graphical-desktop.nix
+++ b/nixos/modules/services/misc/graphical-desktop.nix
@@ -42,6 +42,8 @@ in
 
     programs.gnupg.agent.pinentryPackage = lib.mkOverride 1100 pkgs.pinentry-gnome3;
 
+    services.speechd.enable = lib.mkDefault true;
+
     systemd.defaultUnit = lib.mkIf (xcfg.autorun || dmcfg.enable) "graphical.target";
 
     xdg = {
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index 8b5011ce0d814..a843f400b0314 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -11,6 +11,14 @@ in
     services.jackett = {
       enable = mkEnableOption "Jackett, API support for your favorite torrent trackers";
 
+      port = mkOption {
+        default = 9117;
+        type = types.port;
+        description = ''
+          Port serving the web interface
+        '';
+      };
+
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/jackett/.config/Jackett";
@@ -53,13 +61,13 @@ in
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
+        ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --Port ${toString cfg.port} --DataFolder '${cfg.dataDir}'";
         Restart = "on-failure";
       };
     };
 
     networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ 9117 ];
+      allowedTCPPorts = [ cfg.port ];
     };
 
     users.users = mkIf (cfg.user == "jackett") {
diff --git a/nixos/modules/services/misc/jellyseerr.nix b/nixos/modules/services/misc/jellyseerr.nix
index 7599a1af33840..9aab517e0493b 100644
--- a/nixos/modules/services/misc/jellyseerr.nix
+++ b/nixos/modules/services/misc/jellyseerr.nix
@@ -9,6 +9,7 @@ in
 
   options.services.jellyseerr = {
     enable = mkEnableOption ''Jellyseerr, a requests manager for Jellyfin'';
+    package = mkPackageOption pkgs "jellyseerr" { };
 
     openFirewall = mkOption {
       type = types.bool;
@@ -32,10 +33,10 @@ in
       serviceConfig = {
         Type = "exec";
         StateDirectory = "jellyseerr";
-        WorkingDirectory = "${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr";
+        WorkingDirectory = "${cfg.package}/libexec/jellyseerr/deps/jellyseerr";
         DynamicUser = true;
-        ExecStart = "${pkgs.jellyseerr}/bin/jellyseerr";
-        BindPaths = [ "/var/lib/jellyseerr/:${pkgs.jellyseerr}/libexec/jellyseerr/deps/jellyseerr/config/" ];
+        ExecStart = lib.getExe cfg.package;
+        BindPaths = [ "/var/lib/jellyseerr/:${cfg.package}/libexec/jellyseerr/deps/jellyseerr/config/" ];
         Restart = "on-failure";
         ProtectHome = true;
         ProtectSystem = "strict";
diff --git a/nixos/modules/services/misc/languagetool.nix b/nixos/modules/services/misc/languagetool.nix
index ba563dace4737..2a7e68c9053a3 100644
--- a/nixos/modules/services/misc/languagetool.nix
+++ b/nixos/modules/services/misc/languagetool.nix
@@ -1,14 +1,17 @@
-{ config, lib, options, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.languagetool;
-  settingsFormat = pkgs.formats.javaProperties {};
-in {
+  settingsFormat = pkgs.formats.javaProperties { };
+in
+{
   options.services.languagetool = {
     enable = mkEnableOption "the LanguageTool server, a multilingual spelling, style, and grammar checker that helps correct or paraphrase texts";
 
+    package = mkPackageOption pkgs "languagetool" { };
+
     port = mkOption {
       type = types.port;
       default = 8081;
@@ -31,7 +34,7 @@ in {
       '';
     };
 
-    settings = lib.mkOption {
+    settings = mkOption {
       type = types.submodule {
         freeformType = settingsFormat.type;
 
@@ -49,11 +52,25 @@ in {
         for supported settings.
       '';
     };
+
+    jrePackage = mkPackageOption pkgs "jre" { };
+
+    jvmOptions = mkOption {
+      description = ''
+        Extra command line options for the JVM running languagetool.
+        More information can be found here: https://docs.oracle.com/en/java/javase/19/docs/specs/man/java.html#standard-options-for-java
+      '';
+      default = [ ];
+      type = types.listOf types.str;
+      example = [
+        "-Xmx512m"
+      ];
+    };
   };
 
   config = mkIf cfg.enable {
 
-    systemd.services.languagetool =  {
+    systemd.services.languagetool = {
       description = "LanguageTool HTTP server";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
@@ -65,13 +82,17 @@ in {
         RestrictNamespaces = [ "" ];
         SystemCallFilter = [ "@system-service" "~ @privileged" ];
         ProtectHome = "yes";
+        Restart = "on-failure";
         ExecStart = ''
-          ${pkgs.languagetool}/bin/languagetool-http-server \
-            --port ${toString cfg.port} \
-            ${optionalString cfg.public "--public"} \
-            ${optionalString (cfg.allowOrigin != null) "--allow-origin ${cfg.allowOrigin}"} \
-            "--config" ${settingsFormat.generate "languagetool.conf" cfg.settings}
-          '';
+          ${cfg.jrePackage}/bin/java \
+            -cp ${cfg.package}/share/languagetool-server.jar \
+            ${toString cfg.jvmOptions} \
+            org.languagetool.server.HTTPServer \
+              --port ${toString cfg.port} \
+              ${optionalString cfg.public "--public"} \
+              ${optionalString (cfg.allowOrigin != null) "--allow-origin ${cfg.allowOrigin}"} \
+              "--config" ${settingsFormat.generate "languagetool.conf" cfg.settings}
+        '';
       };
     };
   };
diff --git a/nixos/modules/services/misc/mame.nix b/nixos/modules/services/misc/mame.nix
index 6c7f08d48be10..38b4dd290ed56 100644
--- a/nixos/modules/services/misc/mame.nix
+++ b/nixos/modules/services/misc/mame.nix
@@ -65,5 +65,5 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/misc/ollama.nix b/nixos/modules/services/misc/ollama.nix
index a0a32f1702bf3..f8dbfe9c5692d 100644
--- a/nixos/modules/services/misc/ollama.nix
+++ b/nixos/modules/services/misc/ollama.nix
@@ -1,75 +1,91 @@
-{ config, lib, pkgs, ... }:
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 let
-  inherit (lib) types mkBefore;
+  inherit (lib) literalExpression types mkBefore;
 
   cfg = config.services.ollama;
-  ollamaPackage = cfg.package.override {
-    inherit (cfg) acceleration;
-  };
+  ollamaPackage = cfg.package.override { inherit (cfg) acceleration; };
+
+  staticUser = cfg.user != null && cfg.group != null;
 in
 {
   imports = [
-    (lib.mkRemovedOptionModule [ "services" "ollama" "listenAddress" ]
-      "Use `services.ollama.host` and `services.ollama.port` instead.")
+    (lib.mkRemovedOptionModule [
+      "services"
+      "ollama"
+      "listenAddress"
+    ] "Use `services.ollama.host` and `services.ollama.port` instead.")
+    (lib.mkRemovedOptionModule [
+      "services"
+      "ollama"
+      "sandbox"
+    ] "Set `services.ollama.user` and `services.ollama.group` instead.")
+    (lib.mkRemovedOptionModule
+      [
+        "services"
+        "ollama"
+        "writablePaths"
+      ]
+      "The `models` directory is now always writable. To make other directories writable, use `systemd.services.ollama.serviceConfig.ReadWritePaths`."
+    )
   ];
 
   options = {
     services.ollama = {
       enable = lib.mkEnableOption "ollama server for local large language models";
       package = lib.mkPackageOption pkgs "ollama" { };
+
+      user = lib.mkOption {
+        type = with types; nullOr str;
+        default = null;
+        example = "ollama";
+        description = ''
+          User account under which to run ollama. Defaults to [`DynamicUser`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser=)
+          when set to `null`.
+
+          The user will automatically be created, if this option is set to a non-null value.
+        '';
+      };
+
+      group = lib.mkOption {
+        type = with types; nullOr str;
+        default = cfg.user;
+        defaultText = literalExpression "config.services.ollama.user";
+        example = "ollama";
+        description = ''
+          Group under which to run ollama. Only used when `services.ollama.user` is set.
+
+          The group will automatically be created, if this option is set to a non-null value.
+        '';
+      };
+
       home = lib.mkOption {
         type = types.str;
-        default = "%S/ollama";
+        default = "/var/lib/ollama";
         example = "/home/foo";
         description = ''
           The home directory that the ollama service is started in.
-
-          See also `services.ollama.writablePaths` and `services.ollama.sandbox`.
         '';
       };
+
       models = lib.mkOption {
         type = types.str;
-        default = "%S/ollama/models";
+        default = "${cfg.home}/models";
+        defaultText = "\${config.services.ollama.home}/models";
         example = "/path/to/ollama/models";
         description = ''
           The directory that the ollama service will read models from and download new models to.
-
-          See also `services.ollama.writablePaths` and `services.ollama.sandbox`
-          if downloading models or other mutation of the filesystem is required.
         '';
       };
-      sandbox = lib.mkOption {
-        type = types.bool;
-        default = true;
-        example = false;
-        description = ''
-          Whether to enable systemd's sandboxing capabilities.
 
-          This sets [`DynamicUser`](
-          https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser=
-          ), which runs the server as a unique user with read-only access to most of the filesystem.
-
-          See also `services.ollama.writablePaths`.
-        '';
-      };
-      writablePaths = lib.mkOption {
-        type = types.listOf types.str;
-        default = [ ];
-        example = [ "/home/foo" "/mnt/foo" ];
-        description = ''
-          Paths that the server should have write access to.
-
-          This sets [`ReadWritePaths`](
-          https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#ReadWritePaths=
-          ), which allows specified paths to be written to through the default sandboxing.
-
-          See also `services.ollama.sandbox`.
-        '';
-      };
       host = lib.mkOption {
         type = types.str;
         default = "127.0.0.1";
-        example = "0.0.0.0";
+        example = "[::]";
         description = ''
           The host address which the ollama server HTTP interface listens to.
         '';
@@ -83,7 +99,13 @@ in
         '';
       };
       acceleration = lib.mkOption {
-        type = types.nullOr (types.enum [ false "rocm" "cuda" ]);
+        type = types.nullOr (
+          types.enum [
+            false
+            "rocm"
+            "cuda"
+          ]
+        );
         default = null;
         example = "rocm";
         description = ''
@@ -149,23 +171,90 @@ in
   };
 
   config = lib.mkIf cfg.enable {
+    users = lib.mkIf staticUser {
+      users.${cfg.user} = {
+        inherit (cfg) home;
+        isSystemUser = true;
+        group = cfg.group;
+      };
+      groups.${cfg.group} = { };
+    };
+
     systemd.services.ollama = {
       description = "Server for local large language models";
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-      environment = cfg.environmentVariables // {
-        HOME = cfg.home;
-        OLLAMA_MODELS = cfg.models;
-        OLLAMA_HOST = "${cfg.host}:${toString cfg.port}";
-        HSA_OVERRIDE_GFX_VERSION = lib.mkIf (cfg.rocmOverrideGfx != null) cfg.rocmOverrideGfx;
-      };
-      serviceConfig = {
-        ExecStart = "${lib.getExe ollamaPackage} serve";
-        WorkingDirectory = cfg.home;
-        StateDirectory = [ "ollama" ];
-        DynamicUser = cfg.sandbox;
-        ReadWritePaths = cfg.writablePaths;
-      };
+      environment =
+        cfg.environmentVariables
+        // {
+          HOME = cfg.home;
+          OLLAMA_MODELS = cfg.models;
+          OLLAMA_HOST = "${cfg.host}:${toString cfg.port}";
+        }
+        // lib.optionalAttrs (cfg.rocmOverrideGfx != null) {
+          HSA_OVERRIDE_GFX_VERSION = cfg.rocmOverrideGfx;
+        };
+      serviceConfig =
+        lib.optionalAttrs staticUser {
+          User = cfg.user;
+          Group = cfg.group;
+        }
+        // {
+          DynamicUser = true;
+          ExecStart = "${lib.getExe ollamaPackage} serve";
+          WorkingDirectory = cfg.home;
+          StateDirectory = [ "ollama" ];
+          ReadWritePaths = [
+            cfg.home
+            cfg.models
+          ];
+
+          CapabilityBoundingSet = [ "" ];
+          DeviceAllow = [
+            # CUDA
+            # https://docs.nvidia.com/dgx/pdf/dgx-os-5-user-guide.pdf
+            "char-nvidiactl"
+            "char-nvidia-caps"
+            "char-nvidia-frontend"
+            "char-nvidia-uvm"
+            # ROCm
+            "char-drm"
+            "char-kfd"
+          ];
+          DevicePolicy = "closed";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateDevices = false; # hides acceleration devices
+          PrivateTmp = true;
+          PrivateUsers = true;
+          ProcSubset = "all"; # /proc/meminfo
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+            "AF_UNIX"
+          ];
+          SupplementaryGroups = [ "render" ]; # for rocm to access /dev/dri/renderD* devices
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "@system-service @resources"
+            "~@privileged"
+          ];
+          UMask = "0077";
+        };
       postStart = mkBefore ''
         set -x
         export OLLAMA_HOST=${lib.escapeShellArg cfg.host}:${builtins.toString cfg.port}
@@ -181,5 +270,8 @@ in
     environment.systemPackages = [ ollamaPackage ];
   };
 
-  meta.maintainers = with lib.maintainers; [ abysssol onny ];
+  meta.maintainers = with lib.maintainers; [
+    abysssol
+    onny
+  ];
 }
diff --git a/nixos/modules/services/misc/open-webui.nix b/nixos/modules/services/misc/open-webui.nix
index b4016d03f675f..e2ed7080c9223 100644
--- a/nixos/modules/services/misc/open-webui.nix
+++ b/nixos/modules/services/misc/open-webui.nix
@@ -54,7 +54,10 @@ in
             WEBUI_AUTH = "False";
           }
         '';
-        description = "Extra environment variables for Open-WebUI";
+        description = ''
+          Extra environment variables for Open-WebUI.
+          For more details see https://docs.openwebui.com/getting-started/env-configuration/
+        '';
       };
 
       openFirewall = lib.mkOption {
diff --git a/nixos/modules/services/misc/private-gpt.nix b/nixos/modules/services/misc/private-gpt.nix
index ad9b6f5ffa80f..7bd2e492f5b56 100644
--- a/nixos/modules/services/misc/private-gpt.nix
+++ b/nixos/modules/services/misc/private-gpt.nix
@@ -117,5 +117,5 @@ in
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/misc/radicle.nix b/nixos/modules/services/misc/radicle.nix
new file mode 100644
index 0000000000000..3a393bf0f1f20
--- /dev/null
+++ b/nixos/modules/services/misc/radicle.nix
@@ -0,0 +1,358 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.radicle;
+
+  json = pkgs.formats.json { };
+
+  env = rec {
+    # rad fails if it cannot stat $HOME/.gitconfig
+    HOME = "/var/lib/radicle";
+    RAD_HOME = HOME;
+  };
+
+  # Convenient wrapper to run `rad` in the namespaces of `radicle-node.service`
+  rad-system = pkgs.writeShellScriptBin "rad-system" ''
+    set -o allexport
+    ${toShellVars env}
+    # Note that --env is not used to preserve host's envvars like $TERM
+    exec ${getExe' pkgs.util-linux "nsenter"} -a \
+      -t "$(${getExe' config.systemd.package "systemctl"} show -P MainPID radicle-node.service)" \
+      -S "$(${getExe' config.systemd.package "systemctl"} show -P UID radicle-node.service)" \
+      -G "$(${getExe' config.systemd.package "systemctl"} show -P GID radicle-node.service)" \
+      ${getExe' cfg.package "rad"} "$@"
+  '';
+
+  commonServiceConfig = serviceName: {
+    environment = env // {
+      RUST_LOG = mkDefault "info";
+    };
+    path = [
+      pkgs.gitMinimal
+    ];
+    documentation = [
+      "https://docs.radicle.xyz/guides/seeder"
+    ];
+    after = [
+      "network.target"
+      "network-online.target"
+    ];
+    requires = [
+      "network-online.target"
+    ];
+    wantedBy = [ "multi-user.target" ];
+    serviceConfig = mkMerge [
+      {
+        BindReadOnlyPaths = [
+          "${cfg.configFile}:${env.RAD_HOME}/config.json"
+          "${if types.path.check cfg.publicKey then cfg.publicKey else pkgs.writeText "radicle.pub" cfg.publicKey}:${env.RAD_HOME}/keys/radicle.pub"
+        ];
+        KillMode = "process";
+        StateDirectory = [ "radicle" ];
+        User = config.users.users.radicle.name;
+        Group = config.users.groups.radicle.name;
+        WorkingDirectory = env.HOME;
+      }
+      # The following options are only for optimizing:
+      # systemd-analyze security ${serviceName}
+      {
+        BindReadOnlyPaths = [
+          "-/etc/resolv.conf"
+          "/etc/ssl/certs/ca-certificates.crt"
+          "/run/systemd"
+        ];
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "";
+        DeviceAllow = ""; # ProtectClock= adds DeviceAllow=char-rtc r
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RuntimeDirectoryMode = "700";
+        SocketBindDeny = [ "any" ];
+        StateDirectoryMode = "0750";
+        SystemCallFilter = [
+          "@system-service"
+          "~@aio"
+          "~@chown"
+          "~@keyring"
+          "~@memlock"
+          "~@privileged"
+          "~@resources"
+          "~@setuid"
+          "~@timer"
+        ];
+        SystemCallArchitectures = "native";
+        # This is for BindPaths= and BindReadOnlyPaths=
+        # to allow traversal of directories they create inside RootDirectory=
+        UMask = "0066";
+      }
+    ];
+    confinement = {
+      enable = true;
+      mode = "full-apivfs";
+      packages = [
+        pkgs.gitMinimal
+        cfg.package
+        pkgs.iana-etc
+        (getLib pkgs.nss)
+        pkgs.tzdata
+      ];
+    };
+  };
+in
+{
+  options = {
+    services.radicle = {
+      enable = mkEnableOption "Radicle Seed Node";
+      package = mkPackageOption pkgs "radicle-node" { };
+      privateKeyFile = mkOption {
+        # Note that a key encrypted by systemd-creds is not a path but a str.
+        type = with types; either path str;
+        description = ''
+          Absolute file path to an SSH private key,
+          usually generated by `rad auth`.
+
+          If it contains a colon (`:`) the string before the colon
+          is taken as the credential name
+          and the string after as a path encrypted with `systemd-creds`.
+        '';
+      };
+      publicKey = mkOption {
+        type = with types; either path str;
+        description = ''
+          An SSH public key (as an absolute file path or directly as a string),
+          usually generated by `rad auth`.
+        '';
+      };
+      node = {
+        listenAddress = mkOption {
+          type = types.str;
+          default = "[::]";
+          example = "127.0.0.1";
+          description = "The IP address on which `radicle-node` listens.";
+        };
+        listenPort = mkOption {
+          type = types.port;
+          default = 8776;
+          description = "The port on which `radicle-node` listens.";
+        };
+        openFirewall = mkEnableOption "opening the firewall for `radicle-node`";
+        extraArgs = mkOption {
+          type = with types; listOf str;
+          default = [ ];
+          description = "Extra arguments for `radicle-node`";
+        };
+      };
+      configFile = mkOption {
+        type = types.package;
+        internal = true;
+        default = (json.generate "config.json" cfg.settings).overrideAttrs (previousAttrs: {
+          preferLocalBuild = true;
+          # None of the usual phases are run here because runCommandWith uses buildCommand,
+          # so just append to buildCommand what would usually be a checkPhase.
+          buildCommand = previousAttrs.buildCommand + optionalString cfg.checkConfig ''
+            ln -s $out config.json
+            install -D -m 644 /dev/stdin keys/radicle.pub <<<"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBgFMhajUng+Rjj/sCFXI9PzG8BQjru2n7JgUVF1Kbv5 snakeoil"
+            export RAD_HOME=$PWD
+            ${getExe' pkgs.buildPackages.radicle-node "rad"} config >/dev/null || {
+              cat -n config.json
+              echo "Invalid config.json according to rad."
+              echo "Please double-check your services.radicle.settings (producing the config.json above),"
+              echo "some settings may be missing or have the wrong type."
+              exit 1
+            } >&2
+          '';
+        });
+      };
+      checkConfig = mkEnableOption "checking the {file}`config.json` file resulting from {option}`services.radicle.settings`" // { default = true; };
+      settings = mkOption {
+        description = ''
+          See https://app.radicle.xyz/nodes/seed.radicle.garden/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/radicle/src/node/config.rs#L275
+        '';
+        default = { };
+        example = literalExpression ''
+          {
+            web.pinned.repositories = [
+              "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5" # heartwood
+              "rad:z3trNYnLWS11cJWC6BbxDs5niGo82" # rips
+            ];
+          }
+        '';
+        type = types.submodule {
+          freeformType = json.type;
+        };
+      };
+      httpd = {
+        enable = mkEnableOption "Radicle HTTP gateway to radicle-node";
+        package = mkPackageOption pkgs "radicle-httpd" { };
+        listenAddress = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "The IP address on which `radicle-httpd` listens.";
+        };
+        listenPort = mkOption {
+          type = types.port;
+          default = 8080;
+          description = "The port on which `radicle-httpd` listens.";
+        };
+        nginx = mkOption {
+          # Type of a single virtual host, or null.
+          type = types.nullOr (types.submodule (
+            recursiveUpdate (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {
+              options.serverName = {
+                default = "radicle-${config.networking.hostName}.${config.networking.domain}";
+                defaultText = "radicle-\${config.networking.hostName}.\${config.networking.domain}";
+              };
+            }
+          ));
+          default = null;
+          example = literalExpression ''
+            {
+              serverAliases = [
+                "seed.''${config.networking.domain}"
+              ];
+              enableACME = false;
+              useACMEHost = config.networking.domain;
+            }
+          '';
+          description = ''
+            With this option, you can customize an nginx virtual host which already has sensible defaults for `radicle-httpd`.
+            Set to `{}` if you do not need any customization to the virtual host.
+            If enabled, then by default, the {option}`serverName` is
+            `radicle-''${config.networking.hostName}.''${config.networking.domain}`,
+            TLS is active, and certificates are acquired via ACME.
+            If this is set to null (the default), no nginx virtual host will be configured.
+          '';
+        };
+        extraArgs = mkOption {
+          type = with types; listOf str;
+          default = [ ];
+          description = "Extra arguments for `radicle-httpd`";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      systemd.services.radicle-node = mkMerge [
+        (commonServiceConfig "radicle-node")
+        {
+          description = "Radicle Node";
+          documentation = [ "man:radicle-node(1)" ];
+          serviceConfig = {
+            ExecStart = "${getExe' cfg.package "radicle-node"} --force --listen ${cfg.node.listenAddress}:${toString cfg.node.listenPort} ${escapeShellArgs cfg.node.extraArgs}";
+            Restart = mkDefault "on-failure";
+            RestartSec = "30";
+            SocketBindAllow = [ "tcp:${toString cfg.node.listenPort}" ];
+            SystemCallFilter = mkAfter [
+              # Needed by git upload-pack which calls alarm() and setitimer() when providing a rad clone
+              "@timer"
+            ];
+          };
+          confinement.packages = [
+            cfg.package
+          ];
+        }
+        # Give only access to the private key to radicle-node.
+        {
+          serviceConfig =
+            let keyCred = builtins.split ":" "${cfg.privateKeyFile}"; in
+            if length keyCred > 1
+            then {
+              LoadCredentialEncrypted = [ cfg.privateKeyFile ];
+              # Note that neither %d nor ${CREDENTIALS_DIRECTORY} works in BindReadOnlyPaths=
+              BindReadOnlyPaths = [ "/run/credentials/radicle-node.service/${head keyCred}:${env.RAD_HOME}/keys/radicle" ];
+            }
+            else {
+              LoadCredential = [ "radicle:${cfg.privateKeyFile}" ];
+              BindReadOnlyPaths = [ "/run/credentials/radicle-node.service/radicle:${env.RAD_HOME}/keys/radicle" ];
+            };
+        }
+      ];
+
+      environment.systemPackages = [
+        rad-system
+      ];
+
+      networking.firewall = mkIf cfg.node.openFirewall {
+        allowedTCPPorts = [ cfg.node.listenPort ];
+      };
+
+      users = {
+        users.radicle = {
+          description = "Radicle";
+          group = "radicle";
+          home = env.HOME;
+          isSystemUser = true;
+        };
+        groups.radicle = {
+        };
+      };
+    }
+
+    (mkIf cfg.httpd.enable (mkMerge [
+      {
+        systemd.services.radicle-httpd = mkMerge [
+          (commonServiceConfig "radicle-httpd")
+          {
+            description = "Radicle HTTP gateway to radicle-node";
+            documentation = [ "man:radicle-httpd(1)" ];
+            serviceConfig = {
+              ExecStart = "${getExe' cfg.httpd.package "radicle-httpd"} --listen ${cfg.httpd.listenAddress}:${toString cfg.httpd.listenPort} ${escapeShellArgs cfg.httpd.extraArgs}";
+              Restart = mkDefault "on-failure";
+              RestartSec = "10";
+              SocketBindAllow = [ "tcp:${toString cfg.httpd.listenPort}" ];
+              SystemCallFilter = mkAfter [
+                # Needed by git upload-pack which calls alarm() and setitimer() when providing a git clone
+                "@timer"
+              ];
+            };
+          confinement.packages = [
+            cfg.httpd.package
+          ];
+          }
+        ];
+      }
+
+      (mkIf (cfg.httpd.nginx != null) {
+        services.nginx.virtualHosts.${cfg.httpd.nginx.serverName} = lib.mkMerge [
+          cfg.httpd.nginx
+          {
+            forceSSL = mkDefault true;
+            enableACME = mkDefault true;
+            locations."/" = {
+              proxyPass = "http://${cfg.httpd.listenAddress}:${toString cfg.httpd.listenPort}";
+              recommendedProxySettings = true;
+            };
+          }
+        ];
+
+        services.radicle.settings = {
+          node.alias = mkDefault cfg.httpd.nginx.serverName;
+          node.externalAddresses = mkDefault [
+            "${cfg.httpd.nginx.serverName}:${toString cfg.node.listenPort}"
+          ];
+        };
+      })
+    ]))
+  ]);
+
+  meta.maintainers = with lib.maintainers; [
+    julm
+    lorenzleutgeb
+  ];
+}
diff --git a/nixos/modules/services/misc/libreddit.nix b/nixos/modules/services/misc/redlib.nix
index c1f6b276ad9fa..0da85df46bf72 100644
--- a/nixos/modules/services/misc/libreddit.nix
+++ b/nixos/modules/services/misc/redlib.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  cfg = config.services.libreddit;
+  cfg = config.services.redlib;
 
   args = concatStringsSep " " ([
     "--port ${toString cfg.port}"
@@ -11,11 +11,15 @@ let
   ]);
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "libreddit" ] [ "services" "redlib" ])
+  ];
+
   options = {
-    services.libreddit = {
+    services.redlib = {
       enable = mkEnableOption "Private front-end for Reddit";
 
-      package = mkPackageOption pkgs "libreddit" { };
+      package = mkPackageOption pkgs "redlib" { };
 
       address = mkOption {
         default = "0.0.0.0";
@@ -34,14 +38,14 @@ in
       openFirewall = mkOption {
         type = types.bool;
         default = false;
-        description = "Open ports in the firewall for the libreddit web interface";
+        description = "Open ports in the firewall for the redlib web interface";
       };
 
     };
   };
 
   config = mkIf cfg.enable {
-    systemd.services.libreddit = {
+    systemd.services.redlib = {
         description = "Private front-end for Reddit";
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
diff --git a/nixos/modules/services/misc/rkvm.nix b/nixos/modules/services/misc/rkvm.nix
index b149c3d3979f5..ac747253635e8 100644
--- a/nixos/modules/services/misc/rkvm.nix
+++ b/nixos/modules/services/misc/rkvm.nix
@@ -7,7 +7,7 @@ let
   toml = pkgs.formats.toml { };
 in
 {
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 
   options.services.rkvm = {
     enable = mkOption {
diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix
index 1b16ef7958ad2..fc57683de3280 100644
--- a/nixos/modules/services/misc/snapper.nix
+++ b/nixos/modules/services/misc/snapper.nix
@@ -96,48 +96,48 @@ let
     };
 
     TIMELINE_LIMIT_HOURLY = mkOption {
-      type = types.str;
-      default = "10";
+      type = types.int;
+      default = 10;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_DAILY = mkOption {
-      type = types.str;
-      default = "10";
+      type = types.int;
+      default = 10;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_WEEKLY = mkOption {
-      type = types.str;
-      default = "0";
+      type = types.int;
+      default = 0;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_MONTHLY = mkOption {
-      type = types.str;
-      default = "10";
+      type = types.int;
+      default = 10;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_QUARTERLY = mkOption {
-      type = types.str;
-      default = "0";
+      type = types.int;
+      default = 0;
       description = ''
         Limits for timeline cleanup.
       '';
     };
 
     TIMELINE_LIMIT_YEARLY = mkOption {
-      type = types.str;
-      default = "10";
+      type = types.int;
+      default = 10;
       description = ''
         Limits for timeline cleanup.
       '';
@@ -353,4 +353,6 @@ in
       ) (attrNames cfg.configs);
     }
   );
+
+  meta.maintainers = with lib.maintainers; [ Djabx ];
 }
diff --git a/nixos/modules/services/misc/sonarr.nix b/nixos/modules/services/misc/sonarr.nix
index 228a2d48f5a9c..60e73198d60de 100644
--- a/nixos/modules/services/misc/sonarr.nix
+++ b/nixos/modules/services/misc/sonarr.nix
@@ -1,4 +1,4 @@
-{ config, pkgs, lib, ... }:
+{ config, pkgs, lib, utils, ... }:
 
 with lib;
 
@@ -54,7 +54,11 @@ in
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${cfg.package}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'";
+        ExecStart = utils.escapeSystemdExecArgs [
+          (lib.getExe cfg.package)
+          "-nobrowser"
+          "-data=${cfg.dataDir}"
+        ];
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix
index 4429b20174d94..f2798c1f30b45 100644
--- a/nixos/modules/services/misc/sssd.nix
+++ b/nixos/modules/services/misc/sssd.nix
@@ -145,7 +145,7 @@ in {
         # https://github.com/krb5/krb5/blob/krb5-1.19.3-final/src/include/kcm.h#L43
         listenStreams = [ "/var/run/.heim_org.h5l.kcm-socket" ];
       };
-      krb5.libdefaults.default_ccache_name = "KCM:";
+      security.krb5.settings.libdefaults.default_ccache_name = "KCM:";
     })
 
     (mkIf cfg.sshAuthorizedKeysIntegration {
diff --git a/nixos/modules/services/misc/xmr-stak.nix b/nixos/modules/services/misc/xmr-stak.nix
deleted file mode 100644
index 3015e3cb12a87..0000000000000
--- a/nixos/modules/services/misc/xmr-stak.nix
+++ /dev/null
@@ -1,89 +0,0 @@
-{ lib, config, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.xmr-stak;
-
-  pkg = pkgs.xmr-stak.override {
-    inherit (cfg) openclSupport;
-  };
-
-in
-
-{
-  options = {
-    services.xmr-stak = {
-      enable = mkEnableOption "xmr-stak miner";
-      openclSupport = mkEnableOption "support for OpenCL (AMD/ATI graphics cards)";
-
-      extraArgs = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "--noCPU" "--currency monero" ];
-        description = "List of parameters to pass to xmr-stak.";
-      };
-
-      configFiles = mkOption {
-        type = types.attrsOf types.str;
-        default = {};
-        example = literalExpression ''
-          {
-            "config.txt" = '''
-              "verbose_level" : 4,
-              "h_print_time" : 60,
-              "tls_secure_algo" : true,
-            ''';
-            "pools.txt" = '''
-              "currency" : "monero7",
-              "pool_list" :
-              [ { "pool_address" : "pool.supportxmr.com:443",
-                  "wallet_address" : "my-wallet-address",
-                  "rig_id" : "",
-                  "pool_password" : "nixos",
-                  "use_nicehash" : false,
-                  "use_tls" : true,
-                  "tls_fingerprint" : "",
-                  "pool_weight" : 23
-                },
-              ],
-            ''';
-          }
-        '';
-        description = ''
-          Content of config files like config.txt, pools.txt or cpu.txt.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.xmr-stak = {
-      wantedBy = [ "multi-user.target" ];
-      bindsTo = [ "network-online.target" ];
-      after = [ "network-online.target" ];
-
-      preStart = concatStrings (flip mapAttrsToList cfg.configFiles (fn: content: ''
-        ln -sf '${pkgs.writeText "xmr-stak-${fn}" content}' '${fn}'
-      ''));
-
-      serviceConfig = let rootRequired = cfg.openclSupport; in {
-        ExecStart = "${pkg}/bin/xmr-stak ${concatStringsSep " " cfg.extraArgs}";
-        # xmr-stak generates cpu and/or gpu configuration files
-        WorkingDirectory = "/tmp";
-        PrivateTmp = true;
-        DynamicUser = !rootRequired;
-        LimitMEMLOCK = toString (1024*1024);
-      };
-    };
-  };
-
-  imports = [
-    (mkRemovedOptionModule ["services" "xmr-stak" "configText"] ''
-      This option was removed in favour of `services.xmr-stak.configFiles`
-      because the new config file `pools.txt` was introduced. You are
-      now able to define all other config files like cpu.txt or amd.txt.
-    '')
-  ];
-}
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 8db63d5386332..5b0b1448f6856 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -202,10 +202,11 @@ in {
     ];
 
     services = {
-      fcgiwrap.zoneminder = lib.mkIf useNginx {
+      fcgiwrap.instances.zoneminder = lib.mkIf useNginx {
         process.prefork = cfg.cameras;
         process.user = user;
         process.group = group;
+        socket = { inherit (config.services.nginx) user group; };
       };
 
       mysql = lib.mkIf cfg.database.createLocally {
@@ -255,7 +256,7 @@ in {
                   fastcgi_param HTTP_PROXY "";
                   fastcgi_intercept_errors on;
 
-                  fastcgi_pass unix:${config.services.fcgiwrap.zoneminder.socket.address};
+                  fastcgi_pass unix:${config.services.fcgiwrap.instances.zoneminder.socket.address};
                 }
 
                 location /cache/ {
@@ -372,5 +373,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/monitoring/librenms.nix b/nixos/modules/services/monitoring/librenms.nix
index 08a46754e0e86..b06dbe66fbdee 100644
--- a/nixos/modules/services/monitoring/librenms.nix
+++ b/nixos/modules/services/monitoring/librenms.nix
@@ -2,7 +2,7 @@
 
 let
   cfg = config.services.librenms;
-  settingsFormat = pkgs.formats.json {};
+  settingsFormat = pkgs.formats.json { };
   configJson = settingsFormat.generate "librenms-config.json" cfg.settings;
 
   package = pkgs.librenms.override {
@@ -16,12 +16,13 @@ let
     upload_max_filesize = 100M
     date.timezone = "${config.time.timeZone}"
   '';
-  phpIni = pkgs.runCommand "php.ini" {
-    inherit (package) phpPackage;
-    inherit phpOptions;
-    preferLocalBuild = true;
-    passAsFile = [ "phpOptions" ];
-  } ''
+  phpIni = pkgs.runCommand "php.ini"
+    {
+      inherit (package) phpPackage;
+      inherit phpOptions;
+      preferLocalBuild = true;
+      passAsFile = [ "phpOptions" ];
+    } ''
     cat $phpPackage/etc/php.ini $phpOptionsPath > $out
   '';
 
@@ -31,14 +32,20 @@ let
     if [[ "$USER" != ${cfg.user} ]]; then
       sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}'
     fi
-    $sudo ${package}/artisan $*
+    $sudo ${package}/artisan "$@"
   '';
 
   lnmsWrapper = pkgs.writeShellScriptBin "lnms" ''
     cd ${package}
-    exec ${package}/lnms $*
+    sudo=exec
+    if [[ "$USER" != ${cfg.user} ]]; then
+    sudo='exec /run/wrappers/bin/sudo -u ${cfg.user}'
+    fi
+    $sudo ${package}/lnms "$@"
   '';
 
+
+
   configFile = pkgs.writeText "config.php" ''
     <?php
     $new_config = json_decode(file_get_contents("${cfg.dataDir}/config.json"), true);
@@ -47,7 +54,8 @@ let
     ${lib.optionalString (cfg.extraConfig != null) cfg.extraConfig}
   '';
 
-in {
+in
+{
   options.services.librenms = with lib; {
     enable = mkEnableOption "LibreNMS network monitoring system";
 
@@ -191,7 +199,8 @@ in {
     nginx = mkOption {
       type = types.submodule (
         recursiveUpdate
-          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; }) {}
+          (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
+          { }
       );
       default = { };
       example = literalExpression ''
@@ -240,6 +249,7 @@ in {
         default = "localhost";
         description = ''
           Hostname or IP of the MySQL/MariaDB server.
+          Ignored if 'socket' is defined.
         '';
       };
 
@@ -248,6 +258,7 @@ in {
         default = 3306;
         description = ''
           Port of the MySQL/MariaDB server.
+          Ignored if 'socket' is defined.
         '';
       };
 
@@ -264,15 +275,28 @@ in {
         default = "librenms";
         description = ''
           Name of the user on the MySQL/MariaDB server.
+          Ignored if 'socket' is defined.
         '';
       };
 
       passwordFile = mkOption {
-        type = types.path;
+        type = types.nullOr types.path;
+        default = null;
         example = "/run/secrets/mysql.pass";
         description = ''
           A file containing the password for the user of the MySQL/MariaDB server.
           Must be readable for the LibreNMS user.
+          Ignored if 'socket' is defined, mandatory otherwise.
+        '';
+      };
+
+      socket = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/run/mysqld/mysqld.sock";
+        description = ''
+          A unix socket to mysql, accessible by the librenms user.
+          Useful when mysql is on the localhost.
         '';
       };
     };
@@ -289,7 +313,7 @@ in {
     settings = mkOption {
       type = types.submodule {
         freeformType = settingsFormat.type;
-        options = {};
+        options = { };
       };
       description = ''
         Attrset of the LibreNMS configuration.
@@ -483,13 +507,15 @@ in {
         EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
         User = cfg.user;
         Group = cfg.group;
-        ExecStartPre = lib.mkIf cfg.database.createLocally [ "!${pkgs.writeShellScript "librenms-db-init" ''
+        ExecStartPre = lib.mkIf cfg.database.createLocally [
+          "!${pkgs.writeShellScript "librenms-db-init" ''
           DB_PASSWORD=$(cat ${cfg.database.passwordFile} | tr -d '\n')
           echo "ALTER USER '${cfg.database.username}'@'localhost' IDENTIFIED BY '$DB_PASSWORD';" | ${pkgs.mariadb}/bin/mysql
           ${lib.optionalString cfg.useDistributedPollers ''
             echo "ALTER USER '${cfg.database.username}'@'%' IDENTIFIED BY '$DB_PASSWORD';" | ${pkgs.mariadb}/bin/mysql
           ''}
-        ''}"];
+        ''}"
+        ];
       };
       script = ''
         set -euo pipefail
@@ -516,13 +542,24 @@ in {
         ${lib.optionalString (cfg.useDistributedPollers || cfg.distributedPoller.enable) ''
           echo "CACHE_DRIVER=memcached" >> ${cfg.dataDir}/.env
         ''}
-        echo "DB_HOST=${cfg.database.host}" >> ${cfg.dataDir}/.env
-        echo "DB_PORT=${toString cfg.database.port}" >> ${cfg.dataDir}/.env
         echo "DB_DATABASE=${cfg.database.database}" >> ${cfg.dataDir}/.env
-        echo "DB_USERNAME=${cfg.database.username}" >> ${cfg.dataDir}/.env
-        echo -n "DB_PASSWORD=" >> ${cfg.dataDir}/.env
-        cat ${cfg.database.passwordFile} >> ${cfg.dataDir}/.env
-
+      ''
+      + (
+        if ! isNull cfg.database.socket
+        then ''
+          # use socket connection
+          echo "DB_SOCKET=${cfg.database.socket}" >> ${cfg.dataDir}/.env
+        ''
+        else ''
+          # use TCP connection
+          echo "DB_HOST=${cfg.database.host}" >> ${cfg.dataDir}/.env
+          echo "DB_PORT=${toString cfg.database.port}" >> ${cfg.dataDir}/.env
+          echo "DB_USERNAME=${cfg.database.username}" >> ${cfg.dataDir}/.env
+          echo -n "DB_PASSWORD=" >> ${cfg.dataDir}/.env
+          cat ${cfg.database.passwordFile} >> ${cfg.dataDir}/.env
+        ''
+      )
+      + ''
         # clear cache after update
         OLD_VERSION=$(cat ${cfg.dataDir}/version)
         if [[ $OLD_VERSION != "${package.version}" ]]; then
@@ -560,29 +597,31 @@ in {
 
     services.cron = {
       enable = true;
-      systemCronJobs = let
-        env = "PHPRC=${phpIni}";
-      in [
-        # based on crontab provided by LibreNMS
-        "33 */6 * * * ${cfg.user} ${env} ${package}/cronic ${package}/discovery-wrapper.py 1"
-        "*/5 * * * * ${cfg.user} ${env} ${package}/discovery.php -h new >> /dev/null 2>&1"
-
-        "${if cfg.enableOneMinutePolling then "*" else "*/5"} * * * * ${cfg.user} ${env} ${package}/cronic ${package}/poller-wrapper.py ${toString cfg.pollerThreads}"
-        "* * * * * ${cfg.user} ${env} ${package}/alerts.php >> /dev/null 2>&1"
-
-        "*/5 * * * * ${cfg.user} ${env} ${package}/poll-billing.php >> /dev/null 2>&1"
-        "01 * * * * ${cfg.user} ${env} ${package}/billing-calculate.php >> /dev/null 2>&1"
-        "*/5 * * * * ${cfg.user} ${env} ${package}/check-services.php >> /dev/null 2>&1"
-
-        # extra: fast ping
-        "* * * * * ${cfg.user} ${env} ${package}/ping.php >> /dev/null 2>&1"
-
-        # daily.sh tasks are split to exclude update
-        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh cleanup >> /dev/null 2>&1"
-        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh notifications >> /dev/null 2>&1"
-        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh peeringdb >> /dev/null 2>&1"
-        "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh mac_oui >> /dev/null 2>&1"
-      ];
+      systemCronJobs =
+        let
+          env = "PHPRC=${phpIni}";
+        in
+        [
+          # based on crontab provided by LibreNMS
+          "33 */6 * * * ${cfg.user} ${env} ${package}/cronic ${package}/discovery-wrapper.py 1"
+          "*/5 * * * * ${cfg.user} ${env} ${package}/discovery.php -h new >> /dev/null 2>&1"
+
+          "${if cfg.enableOneMinutePolling then "*" else "*/5"} * * * * ${cfg.user} ${env} ${package}/cronic ${package}/poller-wrapper.py ${toString cfg.pollerThreads}"
+          "* * * * * ${cfg.user} ${env} ${package}/alerts.php >> /dev/null 2>&1"
+
+          "*/5 * * * * ${cfg.user} ${env} ${package}/poll-billing.php >> /dev/null 2>&1"
+          "01 * * * * ${cfg.user} ${env} ${package}/billing-calculate.php >> /dev/null 2>&1"
+          "*/5 * * * * ${cfg.user} ${env} ${package}/check-services.php >> /dev/null 2>&1"
+
+          # extra: fast ping
+          "* * * * * ${cfg.user} ${env} ${package}/ping.php >> /dev/null 2>&1"
+
+          # daily.sh tasks are split to exclude update
+          "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh cleanup >> /dev/null 2>&1"
+          "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh notifications >> /dev/null 2>&1"
+          "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh peeringdb >> /dev/null 2>&1"
+          "19 0 * * * ${cfg.user} ${env} ${package}/daily.sh mac_oui >> /dev/null 2>&1"
+        ];
     };
 
     security.wrappers = {
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 27fc0a1ff3b9c..e475d41270b10 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -88,7 +88,7 @@ in
 
   options = {
     services.nagios = {
-      enable = mkEnableOption ''[Nagios](https://www.nagios.org/) to monitor your system or network.'';
+      enable = mkEnableOption ''[Nagios](https://www.nagios.org/) to monitor your system or network'';
 
       objectDefs = mkOption {
         description = ''
@@ -175,7 +175,7 @@ in
 
   config = mkIf cfg.enable {
     users.users.nagios = {
-      description = "Nagios user ";
+      description = "Nagios user";
       uid         = config.ids.uids.nagios;
       home        = nagiosState;
       group       = "nagios";
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index 8f89408bdea59..c47da2cc075de 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -247,7 +247,8 @@ in {
         ++ lib.optional cfg.python.enable (pkgs.python3.withPackages cfg.python.extraPackages)
         ++ lib.optional config.virtualisation.libvirtd.enable config.virtualisation.libvirtd.package
         ++ lib.optional config.virtualisation.docker.enable config.virtualisation.docker.package
-        ++ lib.optionals config.virtualisation.podman.enable [ pkgs.jq config.virtualisation.podman.package ];
+        ++ lib.optionals config.virtualisation.podman.enable [ pkgs.jq config.virtualisation.podman.package ]
+        ++ lib.optional config.boot.zfs.enabled config.boot.zfs.package;
       environment = {
         PYTHONPATH = "${cfg.package}/libexec/netdata/python.d/python_modules";
         NETDATA_PIPENAME = "/run/netdata/ipc";
diff --git a/nixos/modules/services/monitoring/opentelemetry-collector.nix b/nixos/modules/services/monitoring/opentelemetry-collector.nix
index 459cc85324902..d9b8c27ccdfe3 100644
--- a/nixos/modules/services/monitoring/opentelemetry-collector.nix
+++ b/nixos/modules/services/monitoring/opentelemetry-collector.nix
@@ -6,8 +6,9 @@ let
   cfg = config.services.opentelemetry-collector;
   opentelemetry-collector = cfg.package;
 
-  settingsFormat = pkgs.formats.yaml {};
-in {
+  settingsFormat = pkgs.formats.yaml { };
+in
+{
   options.services.opentelemetry-collector = {
     enable = mkEnableOption "Opentelemetry Collector";
 
@@ -15,7 +16,7 @@ in {
 
     settings = mkOption {
       type = settingsFormat.type;
-      default = {};
+      default = { };
       description = ''
         Specify the configuration for Opentelemetry Collector in Nix.
 
@@ -35,9 +36,9 @@ in {
   config = mkIf cfg.enable {
     assertions = [{
       assertion = (
-        (cfg.settings == {}) != (cfg.configFile == null)
+        (cfg.settings == { }) != (cfg.configFile == null)
       );
-      message  = ''
+      message = ''
         Please specify a configuration for Opentelemetry Collector with either
         'services.opentelemetry-collector.settings' or
         'services.opentelemetry-collector.configFile'.
@@ -48,21 +49,27 @@ in {
       description = "Opentelemetry Collector Service Daemon";
       wantedBy = [ "multi-user.target" ];
 
-      serviceConfig = let
-        conf = if cfg.configFile == null
-               then settingsFormat.generate "config.yaml" cfg.settings
-               else cfg.configFile;
-      in
-      {
-        ExecStart = "${getExe opentelemetry-collector} --config=file:${conf}";
-        DynamicUser = true;
-        Restart = "always";
-        ProtectSystem = "full";
-        DevicePolicy = "closed";
-        NoNewPrivileges = true;
-        WorkingDirectory = "/var/lib/opentelemetry-collector";
-        StateDirectory = "opentelemetry-collector";
-      };
+      serviceConfig =
+        let
+          conf =
+            if cfg.configFile == null
+            then settingsFormat.generate "config.yaml" cfg.settings
+            else cfg.configFile;
+        in
+        {
+          ExecStart = "${getExe opentelemetry-collector} --config=file:${conf}";
+          DynamicUser = true;
+          Restart = "always";
+          ProtectSystem = "full";
+          DevicePolicy = "closed";
+          NoNewPrivileges = true;
+          WorkingDirectory = "%S/opentelemetry-collector";
+          StateDirectory = "opentelemetry-collector";
+          SupplementaryGroups = [
+            # allow to read the systemd journal for opentelemetry-collector
+            "systemd-journal"
+          ];
+        };
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix b/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix
index b4307a76e1b02..b3665b66ba406 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager-webhook-logger.nix
@@ -32,9 +32,15 @@ in
           ${escapeShellArgs cfg.extraFlags}
         '';
 
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "" ];
         DynamicUser = true;
         NoNewPrivileges = true;
 
+        MemoryDenyWriteExecute = true;
+
+        LockPersonality = true;
+
         ProtectProc = "invisible";
         ProtectSystem = "strict";
         ProtectHome = "tmpfs";
@@ -43,6 +49,8 @@ in
         PrivateDevices = true;
         PrivateIPC = true;
 
+        ProcSubset = "pid";
+
         ProtectHostname = true;
         ProtectClock = true;
         ProtectKernelTunables = true;
@@ -50,7 +58,10 @@ in
         ProtectKernelLogs = true;
         ProtectControlGroups = true;
 
+        Restart  = "on-failure";
+
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
         RestrictRealtime = true;
         RestrictSUIDSGID = true;
 
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index d1d8f2caaf63d..f40ac3c9138ff 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -181,15 +181,57 @@ in {
                                                     -i "${alertmanagerYml}"
         '';
         serviceConfig = {
-          Restart  = "always";
-          StateDirectory = "alertmanager";
-          DynamicUser = true; # implies PrivateTmp
-          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
-          WorkingDirectory = "/tmp";
           ExecStart = "${cfg.package}/bin/alertmanager" +
             optionalString (length cmdlineArgs != 0) (" \\\n  " +
               concatStringsSep " \\\n  " cmdlineArgs);
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
+          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+
+          CapabilityBoundingSet = [ "" ];
+          DeviceAllow = [ "" ];
+          DynamicUser = true;
+          NoNewPrivileges = true;
+
+          MemoryDenyWriteExecute = true;
+
+          LockPersonality = true;
+
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          ProtectHome = "tmpfs";
+
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateIPC = true;
+
+          ProcSubset = "pid";
+
+          ProtectHostname = true;
+          ProtectClock = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectKernelLogs = true;
+          ProtectControlGroups = true;
+
+          Restart  = "always";
+
+          RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+
+          StateDirectory = "alertmanager";
+          SystemCallFilter = [
+            "@system-service"
+            "~@cpu-emulation"
+            "~@privileged"
+            "~@reboot"
+            "~@setuid"
+            "~@swap"
+          ];
+
+          WorkingDirectory = "/tmp";
         };
       };
     })
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index dc357f6cc5fb3..723116d2547be 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -27,8 +27,10 @@ let
     "bird"
     "bitcoin"
     "blackbox"
+    "borgmatic"
     "buildkite-agent"
     "collectd"
+    "deluge"
     "dmarc"
     "dnsmasq"
     "dnssec"
@@ -408,6 +410,14 @@ in
         Please ensure you have either `services.prometheus.exporters.idrac.configuration'
           or `services.prometheus.exporters.idrac.configurationPath' set!
       '';
+    } {
+      assertion = cfg.deluge.enable -> (
+        (cfg.deluge.delugePassword == null) != (cfg.deluge.delugePasswordFile == null)
+      );
+      message = ''
+        Please ensure you have either `services.prometheus.exporters.deluge.delugePassword'
+          or `services.prometheus.exporters.deluge.delugePasswordFile' set!
+      '';
     } ] ++ (flip map (attrNames exporterOpts) (exporter: {
       assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
       message = ''
@@ -437,6 +447,13 @@ in
     hardware.rtl-sdr.enable = mkDefault true;
   })] ++ [(mkIf config.services.postfix.enable {
     services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup;
+  })] ++ [(mkIf config.services.prometheus.exporters.deluge.enable {
+    system.activationScripts = {
+      deluge-exported.text = ''
+      mkdir -p /etc/deluge-exporter
+      echo "DELUGE_PASSWORD=$(cat ${config.services.prometheus.exporters.deluge.delugePasswordFile})" > /etc/deluge-exporter/password
+      '';
+    };
   })] ++ (mapAttrsToList (name: conf:
     mkExporterConf {
       inherit name;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/borgmatic.nix b/nixos/modules/services/monitoring/prometheus/exporters/borgmatic.nix
new file mode 100644
index 0000000000000..573230c3afcab
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/borgmatic.nix
@@ -0,0 +1,34 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.prometheus.exporters.borgmatic;
+in
+{
+  port = 9996;
+  extraOpts.configFile = lib.mkOption {
+    type = lib.types.path;
+    default = "/etc/borgmatic/config.yaml";
+    description = ''
+      The path to the borgmatic config file
+    '';
+  };
+
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ProtectSystem = false;
+      ProtectHome = lib.mkForce false;
+      ExecStart = ''
+        ${pkgs.prometheus-borgmatic-exporter}/bin/borgmatic-exporter run \
+          --port ${toString cfg.port} \
+          --config ${toString cfg.configFile} \
+          ${lib.concatMapStringsSep " " (f: lib.escapeShellArg f) cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix b/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix
new file mode 100644
index 0000000000000..5943b46eeb5fc
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/deluge.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.prometheus.exporters.deluge;
+  inherit (lib) mkOption types concatStringsSep;
+in
+{
+  port = 9354;
+
+  extraOpts = {
+    delugeHost = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = ''
+        Hostname where deluge server is running.
+      '';
+    };
+
+    delugePort = mkOption {
+      type = types.port;
+      default = 58846;
+      description = ''
+        Port where deluge server is listening.
+      '';
+    };
+
+    delugeUser = mkOption {
+      type = types.str;
+      default = "localclient";
+      description = ''
+        User to connect to deluge server.
+      '';
+    };
+
+    delugePassword = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Password to connect to deluge server.
+
+        This stores the password unencrypted in the nix store and is thus considered unsafe. Prefer
+        using the delugePasswordFile option.
+      '';
+    };
+
+    delugePasswordFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        File containing the password to connect to deluge server.
+      '';
+    };
+
+    exportPerTorrentMetrics = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable per-torrent metrics.
+
+        This may significantly increase the number of time series depending on the number of
+        torrents in your Deluge instance.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-deluge-exporter}/bin/deluge-exporter
+      '';
+      Environment = [
+        "LISTEN_PORT=${toString cfg.port}"
+        "LISTEN_ADDRESS=${toString cfg.listenAddress}"
+
+        "DELUGE_HOST=${cfg.delugeHost}"
+        "DELUGE_USER=${cfg.delugeUser}"
+        "DELUGE_PORT=${toString cfg.delugePort}"
+      ] ++ lib.optionals (cfg.delugePassword != null) [
+        "DELUGE_PASSWORD=${cfg.delugePassword}"
+      ] ++ lib.optionals cfg.exportPerTorrentMetrics [
+        "PER_TORRENT_METRICS=1"
+      ];
+      EnvironmentFile = lib.optionalString (cfg.delugePasswordFile != null) "/etc/deluge-exporter/password";
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
index 2b4fd12895a39..63d25f3d88fb3 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginxlog.nix
@@ -7,7 +7,27 @@ in {
   port = 9117;
   extraOpts = {
     settings = mkOption {
-      type = types.attrs;
+      type = types.submodule {
+        options = {
+          consul = mkOption {
+            default = null;
+            type = types.nullOr (types.attrsOf types.anything);
+            description = ''
+              Consul integration options. For more information see the [example config](https://github.com/martin-helmich/prometheus-nginxlog-exporter#configuration-file).
+
+              This is disabled by default.
+            '';
+          };
+          namespaces = mkOption {
+            default = [];
+            type = types.listOf (types.attrsOf types.anything);
+
+            description = ''
+              Namespaces to collect the metrics for. For more information see the [example config](https://github.com/martin-helmich/prometheus-nginxlog-exporter#configuration-file).
+            '';
+          };
+        };
+      };
       default = {};
       description = ''
         All settings of nginxlog expressed as an Nix attrset.
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
index 8928577b69532..fa5852a4999dd 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/pve.nix
@@ -114,6 +114,13 @@ in
           Collect PVE onboot status
         '';
       };
+      replication = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Collect PVE replication info
+        '';
+      };
     };
   };
   serviceOpts = {
@@ -128,6 +135,7 @@ in
           --${optionalString (!cfg.collectors.cluster) "no-"}collector.cluster \
           --${optionalString (!cfg.collectors.resources) "no-"}collector.resources \
           --${optionalString (!cfg.collectors.config) "no-"}collector.config \
+          --${optionalString (!cfg.collectors.replication) "no-"}collector.replication \
           ${optionalString (cfg.server.keyFile != null) "--server.keyfile ${cfg.server.keyFile}"} \
           ${optionalString (cfg.server.certFile != null) "--server.certfile ${cfg.server.certFile}"} \
           --config.file %d/configFile \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix b/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
index 8aadd87abbedb..e3dcc6126ff12 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/smartctl.nix
@@ -2,20 +2,23 @@
 
 let
   cfg = config.services.prometheus.exporters.smartctl;
+
   inherit (lib) mkOption types literalExpression;
+
   args = lib.escapeShellArgs ([
     "--web.listen-address=${cfg.listenAddress}:${toString cfg.port}"
-    "--smartctl.path=${pkgs.smartmontools}/bin/smartctl"
     "--smartctl.interval=${cfg.maxInterval}"
   ] ++ map (device: "--smartctl.device=${device}") cfg.devices
   ++ cfg.extraFlags);
-in {
+
+in
+{
   port = 9633;
 
   extraOpts = {
     devices = mkOption {
       type = types.listOf types.str;
-      default = [];
+      default = [ ];
       example = literalExpression ''
         [ "/dev/sda", "/dev/nvme0n1" ];
       '';
@@ -24,6 +27,7 @@ in {
         all disks if none given.
       '';
     };
+
     maxInterval = mkOption {
       type = types.str;
       default = "60s";
@@ -50,9 +54,7 @@ in {
         "block-sd rw"
         "char-nvme rw"
       ];
-      ExecStart = ''
-        ${pkgs.prometheus-smartctl-exporter}/bin/smartctl_exporter ${args}
-      '';
+      ExecStart = "${pkgs.prometheus-smartctl-exporter}/bin/smartctl_exporter ${args}";
       PrivateDevices = lib.mkForce false;
       ProtectProc = "invisible";
       ProcSubset = "pid";
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index dc10a9a2f92ea..c647aba95499a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -81,6 +81,33 @@ in
         Only log messages with the given severity or above.
       '';
     };
+
+    environmentFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/root/prometheus-snmp-exporter.env";
+      description = ''
+        EnvironmentFile as defined in {manpage}`systemd.exec(5)`.
+
+        Secrets may be passed to the service without adding them to the
+        world-readable Nix store, by specifying placeholder variables as
+        the option value in Nix and setting these variables accordingly in the
+        environment file.
+
+        Environment variables from this file will be interpolated into the
+        config file using envsubst with this syntax:
+        `$ENVIRONMENT ''${VARIABLE}`
+
+        For variables to use see [Prometheus Configuration](https://github.com/prometheus/snmp_exporter#prometheus-configuration).
+
+        If the file path is set to this option, the parameter
+        `--config.expand-environment-variables` is implicitly added to
+        `ExecStart`.
+
+        Note that this file needs to be available on the host on which
+        this exporter is running.
+      '';
+    };
   };
   serviceOpts = let
     uncheckedConfigFile = if cfg.configurationPath != null
@@ -92,9 +119,12 @@ in
       uncheckedConfigFile;
     in {
     serviceConfig = {
+      EnvironmentFile = lib.mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
       ExecStart = ''
         ${pkgs.prometheus-snmp-exporter}/bin/snmp_exporter \
           --config.file=${escapeShellArg configFile} \
+          ${lib.optionalString (cfg.environmentFile != null)
+            "--config.expand-environment-variables"} \
           --log.format=${escapeShellArg cfg.logFormat} \
           --log.level=${cfg.logLevel} \
           --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
index 80e2339f59256..d4f9c4a29f386 100644
--- a/nixos/modules/services/monitoring/prometheus/pushgateway.nix
+++ b/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -147,12 +147,52 @@ in {
       wantedBy = [ "multi-user.target" ];
       after    = [ "network.target" ];
       serviceConfig = {
-        Restart  = "always";
-        DynamicUser = true;
         ExecStart = "${cfg.package}/bin/pushgateway" +
           optionalString (length cmdlineArgs != 0) (" \\\n  " +
             concatStringsSep " \\\n  " cmdlineArgs);
+
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "" ];
+        DynamicUser = true;
+        NoNewPrivileges = true;
+
+        MemoryDenyWriteExecute = true;
+
+        LockPersonality = true;
+
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        ProtectHome = "tmpfs";
+
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateIPC = true;
+
+        ProcSubset = "pid";
+
+        ProtectHostname = true;
+        ProtectClock = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectKernelLogs = true;
+        ProtectControlGroups = true;
+
+        Restart  = "always";
+
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+
         StateDirectory = if cfg.persistMetrics then cfg.stateDir else null;
+        SystemCallFilter = [
+          "@system-service"
+          "~@cpu-emulation"
+          "~@privileged"
+          "~@reboot"
+          "~@setuid"
+          "~@swap"
+        ];
       };
     };
   };
diff --git a/nixos/modules/services/monitoring/scrutiny.nix b/nixos/modules/services/monitoring/scrutiny.nix
index c649d333e401a..c0ead07066ec5 100644
--- a/nixos/modules/services/monitoring/scrutiny.nix
+++ b/nixos/modules/services/monitoring/scrutiny.nix
@@ -110,7 +110,10 @@ in
       };
 
       collector = {
-        enable = mkEnableOption "the Scrutiny metrics collector";
+        enable = mkEnableOption "the Scrutiny metrics collector" // {
+          default = cfg.enable;
+          defaultText = lib.literalExpression "config.services.scrutiny.enable";
+        };
 
         package = mkPackageOption pkgs "scrutiny-collector" { };
 
diff --git a/nixos/modules/services/networking/antennas.nix b/nixos/modules/services/networking/antennas.nix
index a37df953fc923..e98b81588044e 100644
--- a/nixos/modules/services/networking/antennas.nix
+++ b/nixos/modules/services/networking/antennas.nix
@@ -38,7 +38,7 @@ in
 
   config = mkIf cfg.enable {
     systemd.services.antennas = {
-      description = "Antennas HDHomeRun emulator for Tvheadend. ";
+      description = "Antennas HDHomeRun emulator for Tvheadend.";
       wantedBy    = [ "multi-user.target" ];
 
       # Config
diff --git a/nixos/modules/services/networking/bee.nix b/nixos/modules/services/networking/bee.nix
index da11ac9399abd..83ce522ba62af 100644
--- a/nixos/modules/services/networking/bee.nix
+++ b/nixos/modules/services/networking/bee.nix
@@ -8,7 +8,7 @@ let
 in {
   meta = {
     # doc = ./bee.xml;
-    maintainers = with maintainers; [ ];
+    maintainers = [ ];
   };
 
   ### interface
@@ -101,8 +101,7 @@ in {
 
       preStart = with cfg.settings; ''
         if ! test -f ${password-file}; then
-          < /dev/urandom tr -dc _A-Z-a-z-0-9 2> /dev/null | head -c32 > ${password-file}
-          chmod 0600 ${password-file}
+          < /dev/urandom tr -dc _A-Z-a-z-0-9 2> /dev/null | head -c32 | install -m 600 /dev/stdin ${password-file}
           echo "Initialized ${password-file} from /dev/urandom"
         fi
         if [ ! -f ${data-dir}/keys/libp2p.key ]; then
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index 03c20f3fe3d36..6c5c59a88dec0 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -271,7 +271,8 @@ in
       '';
 
       serviceConfig = {
-        ExecStart = "${bindPkg.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f";
+        Type = "forking"; # Set type to forking, see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=900788
+        ExecStart = "${bindPkg.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile}";
         ExecReload = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' reload";
         ExecStop = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' stop";
       };
diff --git a/nixos/modules/services/networking/cgit.nix b/nixos/modules/services/networking/cgit.nix
index de8128ed5a59c..cdd316dd99d26 100644
--- a/nixos/modules/services/networking/cgit.nix
+++ b/nixos/modules/services/networking/cgit.nix
@@ -32,7 +32,7 @@ let
       fastcgi_split_path_info ^(${regexLocation cfg})(/.+)$;
       fastcgi_param PATH_INFO $fastcgi_path_info;
     ''
-    }fastcgi_pass unix:${config.services.fcgiwrap."cgit-${name}".socket.address};
+    }fastcgi_pass unix:${config.services.fcgiwrap.instances."cgit-${name}".socket.address};
   '';
 
   cgitrcLine = name: value: "${name}=${
@@ -171,7 +171,7 @@ in
       groups.${cfg.group} = { };
     }));
 
-    services.fcgiwrap = flip mapAttrs' cfgs (name: cfg:
+    services.fcgiwrap.instances = flip mapAttrs' cfgs (name: cfg:
       nameValuePair "cgit-${name}" {
         process = { inherit (cfg) user group; };
         socket = { inherit (config.services.nginx) user group; };
diff --git a/nixos/modules/services/networking/cloudflare-dyndns.nix b/nixos/modules/services/networking/cloudflare-dyndns.nix
index ab5b1a08539a5..9495c8dcaf810 100644
--- a/nixos/modules/services/networking/cloudflare-dyndns.nix
+++ b/nixos/modules/services/networking/cloudflare-dyndns.nix
@@ -10,6 +10,8 @@ in
     services.cloudflare-dyndns = {
       enable = mkEnableOption "Cloudflare Dynamic DNS Client";
 
+      package = mkPackageOption pkgs "cloudflare-dyndns" { };
+
       apiTokenFile = mkOption {
         type = types.nullOr types.str;
         default = null;
@@ -28,6 +30,16 @@ in
         '';
       };
 
+      frequency = mkOption {
+        type = types.nullOr types.str;
+        default = "*:0/5";
+        description = ''
+          Run cloudflare-dyndns with the given frequency (see
+          {manpage}`systemd.time(7)` for the format).
+          If null, do not run automatically.
+        '';
+      };
+
       proxied = mkOption {
         type = types.bool;
         default = false;
@@ -67,7 +79,6 @@ in
       description = "CloudFlare Dynamic DNS Client";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      startAt = "*:0/5";
 
       environment = {
         CLOUDFLARE_DOMAINS = toString cfg.domains;
@@ -86,8 +97,10 @@ in
               ++ optional cfg.deleteMissing "--delete-missing"
               ++ optional cfg.proxied "--proxied";
           in
-          "${pkgs.cloudflare-dyndns}/bin/cloudflare-dyndns ${toString args}";
+          "${getExe cfg.package} ${toString args}";
       };
+    } // optionalAttrs (cfg.frequency != null) {
+      startAt = cfg.frequency;
     };
   };
 }
diff --git a/nixos/modules/services/networking/cloudflare-warp.nix b/nixos/modules/services/networking/cloudflare-warp.nix
new file mode 100644
index 0000000000000..2ab5f287ac496
--- /dev/null
+++ b/nixos/modules/services/networking/cloudflare-warp.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.cloudflare-warp;
+in
+{
+  options.services.cloudflare-warp = {
+    enable = lib.mkEnableOption "Cloudflare Zero Trust client daemon";
+
+    package = lib.mkPackageOption pkgs "cloudflare-warp" { };
+
+    rootDir = lib.mkOption {
+      type = lib.types.str;
+      default = "/var/lib/cloudflare-warp";
+      description = ''
+        Working directory for the warp-svc daemon.
+      '';
+    };
+
+    udpPort = lib.mkOption {
+      type = lib.types.port;
+      default = 2408;
+      description = ''
+        The UDP port to open in the firewall. Warp uses port 2408 by default, but fallback ports can be used
+        if that conflicts with another service. See the [firewall documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/warp/deployment/firewall#warp-udp-ports)
+        for the pre-configured available fallback ports.
+      '';
+    };
+
+    openFirewall = lib.mkEnableOption "opening UDP ports in the firewall" // {
+      default = true;
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    networking.firewall = lib.mkIf cfg.openFirewall {
+      allowedUDPPorts = [ cfg.udpPort ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${cfg.rootDir}    - root root"
+      "z ${cfg.rootDir}    - root root"
+    ];
+
+    systemd.services.cloudflare-warp = {
+      enable = true;
+      description = "Cloudflare Zero Trust Client Daemon";
+
+      # lsof is used by the service to determine which UDP port to bind to
+      # in the case that it detects collisions.
+      path = [ pkgs.lsof ];
+      requires = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig =
+        let
+          caps = [
+            "CAP_NET_ADMIN"
+            "CAP_NET_BIND_SERVICE"
+            "CAP_SYS_PTRACE"
+          ];
+        in
+        {
+          Type = "simple";
+          ExecStart = "${cfg.package}/bin/warp-svc";
+          ReadWritePaths = [ "${cfg.rootDir}" "/etc/resolv.conf" ];
+          CapabilityBoundingSet = caps;
+          AmbientCapabilities = caps;
+          Restart = "always";
+          RestartSec = 5;
+          Environment = [ "RUST_BACKTRACE=full" ];
+          WorkingDirectory = cfg.rootDir;
+
+          # See the systemd.exec docs for the canonicalized paths, the service
+          # makes use of them for logging, and account state info tracking.
+          # https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#RuntimeDirectory=
+          StateDirectory = "cloudflare-warp";
+          RuntimeDirectory = "cloudflare-warp";
+          LogsDirectory = "cloudflare-warp";
+
+          # The service needs to write to /etc/resolv.conf to configure DNS, so that file would have to
+          # be world read/writable to run as anything other than root.
+          User = "root";
+          Group = "root";
+        };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ treyfortmuller ];
+}
diff --git a/nixos/modules/services/networking/ddns-updater.nix b/nixos/modules/services/networking/ddns-updater.nix
new file mode 100644
index 0000000000000..85daa59fe66de
--- /dev/null
+++ b/nixos/modules/services/networking/ddns-updater.nix
@@ -0,0 +1,46 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.ddns-updater;
+in
+{
+  options.services.ddns-updater = {
+    enable = lib.mkEnableOption "Container to update DNS records periodically with WebUI for many DNS providers";
+
+    package = lib.mkPackageOption pkgs "ddns-updater" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf lib.types.str;
+      description = "Environment variables to be set for the ddns-updater service. DATADIR is ignored to enable using systemd DynamicUser. For full list see https://github.com/qdm12/ddns-updater";
+      default = { };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd.services.ddns-updater = {
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      environment = cfg.environment // {
+        DATADIR = "%S/ddns-updater";
+      };
+      unitConfig = {
+        Description = "DDNS-updater service";
+      };
+      serviceConfig = {
+        TimeoutSec = "5min";
+        ExecStart = lib.getExe cfg.package;
+        RestartSec = 30;
+        DynamicUser = true;
+        StateDirectory = "ddns-updater";
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/deconz.nix b/nixos/modules/services/networking/deconz.nix
index 88b0ee612d871..d8908470bb231 100644
--- a/nixos/modules/services/networking/deconz.nix
+++ b/nixos/modules/services/networking/deconz.nix
@@ -14,7 +14,7 @@ in
 {
   options.services.deconz = {
 
-    enable = lib.mkEnableOption "deCONZ, a Zigbee gateway for use with ConBee hardware (https://phoscon.de/en/conbee2)";
+    enable = lib.mkEnableOption "deCONZ, a Zigbee gateway for use with ConBee/RaspBee hardware (https://phoscon.de/)";
 
     package = lib.mkOption {
       type = lib.types.package;
@@ -122,6 +122,7 @@ in
         RuntimeDirectory = name;
         RuntimeDirectoryMode = "0700";
         StateDirectory = name;
+        SuccessExitStatus = [ 143 ];
         WorkingDirectory = stateDir;
         # For access to /dev/ttyACM0 (ConBee).
         SupplementaryGroups = [ "dialout" ];
diff --git a/nixos/modules/services/networking/eternal-terminal.nix b/nixos/modules/services/networking/eternal-terminal.nix
index f4456f4d99c8b..d26e26d0c1950 100644
--- a/nixos/modules/services/networking/eternal-terminal.nix
+++ b/nixos/modules/services/networking/eternal-terminal.nix
@@ -90,6 +90,6 @@ in
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ ];
+    maintainers = [ ];
   };
 }
diff --git a/nixos/modules/services/networking/firefox-syncserver.nix b/nixos/modules/services/networking/firefox-syncserver.nix
index 674a424fb0a42..2f706c3bc3f98 100644
--- a/nixos/modules/services/networking/firefox-syncserver.nix
+++ b/nixos/modules/services/networking/firefox-syncserver.nix
@@ -316,7 +316,7 @@ in
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ ];
+    maintainers = [ ];
     doc = ./firefox-syncserver.md;
   };
 }
diff --git a/nixos/modules/services/networking/hans.nix b/nixos/modules/services/networking/hans.nix
index 00d276bcdf60a..0d0e2c340ebf1 100644
--- a/nixos/modules/services/networking/hans.nix
+++ b/nixos/modules/services/networking/hans.nix
@@ -141,5 +141,5 @@ in
     };
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/networking/trust-dns.nix b/nixos/modules/services/networking/hickory-dns.nix
index 039b7de263504..6b99686958d72 100644
--- a/nixos/modules/services/networking/trust-dns.nix
+++ b/nixos/modules/services/networking/hickory-dns.nix
@@ -1,9 +1,9 @@
 { config, lib, pkgs, ... }:
 let
-  cfg = config.services.trust-dns;
+  cfg = config.services.hickory-dns;
   toml = pkgs.formats.toml { };
 
-  configFile = toml.generate "trust-dns.toml" (
+  configFile = toml.generate "hickory-dns.toml" (
     lib.filterAttrsRecursive (_: v: v != null) cfg.settings
   );
 
@@ -26,7 +26,7 @@ let
           - "Forward" (a cached zone where all requests are forwarded to another resolver).
 
           For more details about these zone types, consult the documentation for BIND,
-          though note that trust-dns supports only a subset of BIND's zone types:
+          though note that hickory-dns supports only a subset of BIND's zone types:
           <https://bind9.readthedocs.io/en/v9_18_4/reference.html#type>
         '';
       };
@@ -45,10 +45,19 @@ let
 in
 {
   meta.maintainers = with lib.maintainers; [ colinsane ];
+
+  imports = with lib; [
+    (mkRenamedOptionModule [ "services" "trust-dns" "enable" ] [ "services" "hickory-dns" "enable" ])
+    (mkRenamedOptionModule [ "services" "trust-dns" "package" ] [ "services" "hickory-dns" "package" ])
+    (mkRenamedOptionModule [ "services" "trust-dns" "settings" ] [ "services" "hickory-dns" "settings" ])
+    (mkRenamedOptionModule [ "services" "trust-dns" "quiet" ] [ "services" "hickory-dns" "quiet" ])
+    (mkRenamedOptionModule [ "services" "trust-dns" "debug" ] [ "services" "hickory-dns" "debug" ])
+  ];
+
   options = {
-    services.trust-dns = with lib; {
-      enable = mkEnableOption "trust-dns";
-      package = mkPackageOption pkgs "trust-dns" {
+    services.hickory-dns = with lib; {
+      enable = mkEnableOption "hickory-dns";
+      package = mkPackageOption pkgs "hickory-dns" {
         extraDescription = ''
           ::: {.note}
           The package must provide `meta.mainProgram` which names the server binary; any other utilities (client, resolver) are not needed.
@@ -75,9 +84,9 @@ in
       };
       settings = mkOption {
         description = ''
-          Settings for trust-dns. The options enumerated here are not exhaustive.
+          Settings for hickory-dns. The options enumerated here are not exhaustive.
           Refer to upstream documentation for all available options:
-          - [Example settings](https://github.com/bluejekyll/trust-dns/blob/main/tests/test-data/test_configs/example.toml)
+          - [Example settings](https://github.com/hickory-dns/hickory-dns/blob/main/tests/test-data/test_configs/example.toml)
         '';
         type = types.submodule {
           freeformType = toml.type;
@@ -106,9 +115,9 @@ in
             };
             directory = mkOption {
               type = types.str;
-              default = "/var/lib/trust-dns";
+              default = "/var/lib/hickory-dns";
               description = ''
-                The directory in which trust-dns should look for .zone files,
+                The directory in which hickory-dns should look for .zone files,
                 whenever zones aren't specified by absolute path.
               '';
             };
@@ -124,23 +133,23 @@ in
   };
 
   config = lib.mkIf cfg.enable {
-    systemd.services.trust-dns = {
-      description = "trust-dns Domain Name Server";
-      unitConfig.Documentation = "https://trust-dns.org/";
+    systemd.services.hickory-dns = {
+      description = "hickory-dns Domain Name Server";
+      unitConfig.Documentation = "https://hickory-dns.org/";
       serviceConfig = {
         ExecStart =
         let
           flags =  (lib.optional cfg.debug "--debug") ++ (lib.optional cfg.quiet "--quiet");
           flagsStr = builtins.concatStringsSep " " flags;
         in ''
-          ${cfg.package}/bin/${cfg.package.meta.mainProgram} --config ${configFile} ${flagsStr}
+          ${lib.getExe cfg.package} --config ${configFile} ${flagsStr}
         '';
         Type = "simple";
         Restart = "on-failure";
         RestartSec = "10s";
         DynamicUser = true;
 
-        StateDirectory = "trust-dns";
+        StateDirectory = "hickory-dns";
         ReadWritePaths = [ cfg.settings.directory ];
 
         AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index aa059b1b7c90a..2bb9df15de2c3 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -183,37 +183,45 @@ let
   in
     pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
 
-  tunnelConf = let opts = [
-    notice
-    (flip map
-      (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
-      (tun: let outTunOpts = [
-        (sec tun.name)
-        "type = client"
-        (intOpt "port" tun.port)
-        (strOpt "destination" tun.destination)
+  tunnelConf = let
+    mkOutTunnel = tun:
+      let
+        outTunOpts = [
+          (sec tun.name)
+          "type = client"
+          (intOpt "port" tun.port)
+          (strOpt "destination" tun.destination)
         ] ++ (optionals (tun ? destinationPort) (optionalNullInt "destinationport" tun.destinationPort))
-        ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
-        ++ (optionals (tun ? address) (optionalNullString "address" tun.address))
-        ++ (optionals (tun ? inbound.length) (optionalNullInt "inbound.length" tun.inbound.length))
-        ++ (optionals (tun ? inbound.quantity) (optionalNullInt "inbound.quantity" tun.inbound.quantity))
-        ++ (optionals (tun ? outbound.length) (optionalNullInt "outbound.length" tun.outbound.length))
-        ++ (optionals (tun ? outbound.quantity) (optionalNullInt "outbound.quantity" tun.outbound.quantity))
-        ++ (optionals (tun ? crypto.tagsToSend) (optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend));
-        in concatStringsSep "\n" outTunOpts))
-    (flip map
-      (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
-      (tun: let inTunOpts = [
-        (sec tun.name)
-        "type = server"
-        (intOpt "port" tun.port)
-        (strOpt "host" tun.address)
-      ] ++ (optionals (tun ? destination) (optionalNullString "destination" tun.destination))
-        ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
-        ++ (optionals (tun ? inPort) (optionalNullInt "inport" tun.inPort))
-        ++ (optionals (tun ? accessList) (optionalEmptyList "accesslist" tun.accessList));
-        in concatStringsSep "\n" inTunOpts))];
-    in pkgs.writeText "i2pd-tunnels.conf" opts;
+          ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
+          ++ (optionals (tun ? address) (optionalNullString "address" tun.address))
+          ++ (optionals (tun ? inbound.length) (optionalNullInt "inbound.length" tun.inbound.length))
+          ++ (optionals (tun ? inbound.quantity) (optionalNullInt "inbound.quantity" tun.inbound.quantity))
+          ++ (optionals (tun ? outbound.length) (optionalNullInt "outbound.length" tun.outbound.length))
+          ++ (optionals (tun ? outbound.quantity) (optionalNullInt "outbound.quantity" tun.outbound.quantity))
+          ++ (optionals (tun ? crypto.tagsToSend) (optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend));
+      in
+        concatStringsSep "\n" outTunOpts;
+
+    mkInTunnel = tun:
+      let
+        inTunOpts = [
+          (sec tun.name)
+          "type = server"
+          (intOpt "port" tun.port)
+          (strOpt "host" tun.address)
+        ] ++ (optionals (tun ? destination) (optionalNullString "destination" tun.destination))
+          ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
+          ++ (optionals (tun ? inPort) (optionalNullInt "inport" tun.inPort))
+          ++ (optionals (tun ? accessList) (optionalEmptyList "accesslist" tun.accessList));
+      in
+        concatStringsSep "\n" inTunOpts;
+
+    allOutTunnels = collect (tun: tun ? port && tun ? destination) cfg.outTunnels;
+    allInTunnels = collect (tun: tun ? port && tun ? address) cfg.inTunnels;
+
+    opts = [ notice ] ++ (map mkOutTunnel allOutTunnels) ++ (map mkInTunnel allInTunnels);
+  in
+    pkgs.writeText "i2pd-tunnels.conf" (concatStringsSep "\n" opts);
 
   i2pdFlags = concatStringsSep " " (
     optional (cfg.address != null) ("--host=" + cfg.address) ++ [
diff --git a/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
index 03210bca371cf..5b700269037c2 100644
--- a/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
+++ b/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
@@ -1,18 +1,27 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
 
 let
   cfg = config.services.magic-wormhole-mailbox-server;
+  # keep semicolon in dataDir for backward compatibility
   dataDir = "/var/lib/magic-wormhole-mailbox-server;";
-  python = pkgs.python3.withPackages (py: [ py.magic-wormhole-mailbox-server py.twisted ]);
+  python = pkgs.python311.withPackages (
+    py: with py; [
+      magic-wormhole-mailbox-server
+      twisted
+    ]
+  );
 in
 {
   options.services.magic-wormhole-mailbox-server = {
-    enable = mkEnableOption "Magic Wormhole Mailbox Server";
+    enable = lib.mkEnableOption "Magic Wormhole Mailbox Server";
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     systemd.services.magic-wormhole-mailbox-server = {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
@@ -23,6 +32,7 @@ in
         StateDirectory = baseNameOf dataDir;
       };
     };
-
   };
+
+  meta.maintainers = [ lib.maintainers.mjoerg ];
 }
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index 7baaf93a1bcfa..468f58b56bb1c 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -483,7 +483,7 @@ let
 
     listeners = mkOption {
       type = listOf listenerOptions;
-      default = {};
+      default = [];
       description = ''
         Listeners to configure on this broker.
       '';
@@ -721,7 +721,7 @@ in
   };
 
   meta = {
-    maintainers = with lib.maintainers; [ ];
+    maintainers = [ ];
     doc = ./mosquitto.md;
   };
 }
diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix
deleted file mode 100644
index e53fb71788cdc..0000000000000
--- a/nixos/modules/services/networking/mxisd.nix
+++ /dev/null
@@ -1,137 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  isMa1sd =
-    package:
-    lib.hasPrefix "ma1sd" package.name;
-
-  isMxisd =
-    package:
-    lib.hasPrefix "mxisd" package.name;
-
-  cfg = config.services.mxisd;
-
-  server = optionalAttrs (cfg.server.name != null) { inherit (cfg.server) name; }
-        // optionalAttrs (cfg.server.port != null) { inherit (cfg.server) port; };
-
-  baseConfig = {
-    matrix.domain = cfg.matrix.domain;
-    key.path = "${cfg.dataDir}/signing.key";
-    storage = {
-      provider.sqlite.database = if isMa1sd cfg.package
-                                 then "${cfg.dataDir}/ma1sd.db"
-                                 else "${cfg.dataDir}/mxisd.db";
-    };
-  } // optionalAttrs (server != {}) { inherit server; };
-
-  # merges baseConfig and extraConfig into a single file
-  fullConfig = recursiveUpdate baseConfig cfg.extraConfig;
-
-  configFile = if isMa1sd cfg.package
-               then pkgs.writeText "ma1sd-config.yaml" (builtins.toJSON fullConfig)
-               else pkgs.writeText "mxisd-config.yaml" (builtins.toJSON fullConfig);
-
-in {
-  options = {
-    services.mxisd = {
-      enable = mkEnableOption "matrix federated identity server";
-
-      package = mkPackageOption pkgs "ma1sd" { };
-
-      environmentFile = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Path to an environment-file which may contain secrets to be
-          substituted via `envsubst`.
-        '';
-      };
-
-      dataDir = mkOption {
-        type = types.str;
-        default = "/var/lib/mxisd";
-        description = "Where data mxisd/ma1sd uses resides";
-      };
-
-      extraConfig = mkOption {
-        type = types.attrs;
-        default = {};
-        description = "Extra options merged into the mxisd/ma1sd configuration";
-      };
-
-      matrix = {
-
-        domain = mkOption {
-          type = types.str;
-          description = ''
-            the domain of the matrix homeserver
-          '';
-        };
-
-      };
-
-      server = {
-
-        name = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          description = ''
-            Public hostname of mxisd/ma1sd, if different from the Matrix domain.
-          '';
-        };
-
-        port = mkOption {
-          type = types.nullOr types.int;
-          default = null;
-          description = ''
-            HTTP port to listen on (unencrypted)
-          '';
-        };
-
-      };
-
-    };
-  };
-
-  config = mkIf cfg.enable {
-    users.users.mxisd =
-      {
-        group = "mxisd";
-        home = cfg.dataDir;
-        createHome = true;
-        shell = "${pkgs.bash}/bin/bash";
-        uid = config.ids.uids.mxisd;
-      };
-
-    users.groups.mxisd =
-      {
-        gid = config.ids.gids.mxisd;
-      };
-
-    systemd.services.mxisd = {
-      description = "a federated identity server for the matrix ecosystem";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = let
-        executable = if isMa1sd cfg.package then "ma1sd" else "mxisd";
-      in {
-        Type = "simple";
-        User = "mxisd";
-        Group = "mxisd";
-        EnvironmentFile = mkIf (cfg.environmentFile != null) [ cfg.environmentFile ];
-        ExecStart = "${cfg.package}/bin/${executable} -c ${cfg.dataDir}/mxisd-config.yaml";
-        ExecStartPre = "${pkgs.writeShellScript "mxisd-substitute-secrets" ''
-          umask 0077
-          ${pkgs.envsubst}/bin/envsubst -o ${cfg.dataDir}/mxisd-config.yaml \
-            -i ${configFile}
-        ''}";
-        WorkingDirectory = cfg.dataDir;
-        Restart = "on-failure";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/networking/nar-serve.nix b/nixos/modules/services/networking/nar-serve.nix
index b2082032ad90f..5a5cefe86d29f 100644
--- a/nixos/modules/services/networking/nar-serve.nix
+++ b/nixos/modules/services/networking/nar-serve.nix
@@ -12,6 +12,8 @@ in
     services.nar-serve = {
       enable = mkEnableOption "serving NAR file contents via HTTP";
 
+      package = mkPackageOption pkgs "nar-serve" { };
+
       port = mkOption {
         type = types.port;
         default = 8383;
@@ -32,6 +34,17 @@ in
           - gs:// for binary caches stored in Google Cloud Storage
         '';
       };
+
+      domain = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          When set, enables the feature of serving <nar-hash>.<domain>
+          on top of <domain>/nix/store/<nar-hash>-<pname>.
+
+          Useful to preview static websites where paths are absolute.
+        '';
+      };
     };
   };
 
@@ -47,7 +60,7 @@ in
       serviceConfig = {
         Restart = "always";
         RestartSec = "5s";
-        ExecStart = "${pkgs.nar-serve}/bin/nar-serve";
+        ExecStart = lib.getExe cfg.package;
         DynamicUser = true;
       };
     };
diff --git a/nixos/modules/services/networking/nats.nix b/nixos/modules/services/networking/nats.nix
index f159ef068b561..4851daae4dbab 100644
--- a/nixos/modules/services/networking/nats.nix
+++ b/nixos/modules/services/networking/nats.nix
@@ -10,6 +10,13 @@ let
 
   configFile = format.generate "nats.conf" cfg.settings;
 
+  validateConfig = file:
+  pkgs.runCommand "validate-nats-conf" {
+    nativeBuildInputs = [ pkgs.nats-server ];
+  } ''
+    nats-server --config "${configFile}" -t
+    ln -s "${configFile}" "$out"
+  '';
 in {
 
   ### Interface
@@ -104,7 +111,7 @@ in {
         })
         {
           Type = "simple";
-          ExecStart = "${pkgs.nats-server}/bin/nats-server -c ${configFile}";
+          ExecStart = "${pkgs.nats-server}/bin/nats-server -c ${validateConfig configFile}";
           ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
           ExecStop = "${pkgs.coreutils}/bin/kill -SIGINT $MAINPID";
           Restart = "on-failure";
diff --git a/nixos/modules/services/networking/nebula.nix b/nixos/modules/services/networking/nebula.nix
index 56eed04c3e8d9..d69af8d16b52c 100644
--- a/nixos/modules/services/networking/nebula.nix
+++ b/nixos/modules/services/networking/nebula.nix
@@ -51,8 +51,8 @@ in
             };
 
             key = mkOption {
-              type = types.path;
-              description = "Path to the host key.";
+              type = types.oneOf [types.nonEmptyStr types.path];
+              description = "Path or reference to the host key.";
               example = "/etc/nebula/host.key";
             };
 
@@ -241,7 +241,7 @@ in
               ProtectKernelModules = true;
               ProtectKernelTunables = true;
               ProtectProc = "invisible";
-              ProtectSystem = "strict";
+              ProtectSystem = true;
               RestrictNamespaces = true;
               RestrictSUIDSGID = true;
               User = networkId;
@@ -269,4 +269,6 @@ in
       ${nameToId netName} = {};
     }) enabledNetworks);
   };
+
+  meta.maintainers = [ numinit ];
 }
diff --git a/nixos/modules/services/networking/opengfw.nix b/nixos/modules/services/networking/opengfw.nix
new file mode 100644
index 0000000000000..1866e75487dd3
--- /dev/null
+++ b/nixos/modules/services/networking/opengfw.nix
@@ -0,0 +1,414 @@
+{
+  lib,
+  pkgs,
+  config,
+  ...
+}:
+let
+  inherit (lib)
+    mkOption
+    types
+    mkIf
+    optionalString
+    ;
+  cfg = config.services.opengfw;
+in
+{
+  options.services.opengfw = {
+    enable = lib.mkEnableOption ''
+      OpenGFW, A flexible, easy-to-use, open source implementation of GFW on Linux
+    '';
+
+    package = lib.mkPackageOption pkgs "opengfw" { default = "opengfw"; };
+
+    user = mkOption {
+      default = "opengfw";
+      type = types.singleLineStr;
+      description = "Username of the OpenGFW user.";
+    };
+
+    dir = mkOption {
+      default = "/var/lib/opengfw";
+      type = types.singleLineStr;
+      description = ''
+        Working directory of the OpenGFW service and home of `opengfw.user`.
+      '';
+    };
+
+    logFile = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      example = "/var/lib/opengfw/opengfw.log";
+      description = ''
+        File to write the output to instead of systemd.
+      '';
+    };
+
+    logFormat = mkOption {
+      description = ''
+        Format of the logs. [logFormatMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L62)
+      '';
+      default = "json";
+      example = "console";
+      type = types.enum [
+        "json"
+        "console"
+      ];
+    };
+
+    pcapReplay = mkOption {
+      default = null;
+      example = "./opengfw.pcap";
+      type = types.nullOr types.path;
+      description = ''
+        Path to PCAP replay file.
+        In pcap mode, none of the actions in the rules have any effect.
+        This mode is mainly for debugging.
+      '';
+    };
+
+    logLevel = mkOption {
+      description = ''
+        Level of the logs. [logLevelMap](https://github.com/apernet/OpenGFW/blob/d7737e92117a11c9a6100d53019fac3b9d724fe3/cmd/root.go#L55)
+      '';
+      default = "info";
+      example = "warn";
+      type = types.enum [
+        "debug"
+        "info"
+        "warn"
+        "error"
+      ];
+    };
+
+    rulesFile = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = ''
+        Path to file containing OpenGFW rules.
+      '';
+    };
+
+    settingsFile = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = ''
+        Path to file containing OpenGFW settings.
+      '';
+    };
+
+    settings = mkOption {
+      default = null;
+      description = ''
+        Settings passed to OpenGFW. [Example config](https://gfw.dev/docs/build-run/#config-example)
+      '';
+      type = types.nullOr (
+        types.submodule {
+          options = {
+            replay = mkOption {
+              description = ''
+                PCAP replay settings.
+              '';
+              default = { };
+              type = types.submodule {
+                options = {
+                  realtime = mkOption {
+                    description = ''
+                      Whether the packets in the PCAP file should be replayed in "real time" (instead of as fast as possible).
+                    '';
+                    default = false;
+                    example = true;
+                    type = types.bool;
+                  };
+                };
+              };
+            };
+
+            io = mkOption {
+              description = ''
+                IO settings.
+              '';
+              default = { };
+              type = types.submodule {
+                options = {
+                  queueSize = mkOption {
+                    description = "IO queue size.";
+                    type = types.int;
+                    default = 1024;
+                    example = 2048;
+                  };
+                  local = mkOption {
+                    description = ''
+                      Set to false if you want to run OpenGFW on FORWARD chain. (e.g. on a router)
+                    '';
+                    type = types.bool;
+                    default = true;
+                    example = false;
+                  };
+                  rst = mkOption {
+                    description = ''
+                      Set to true if you want to send RST for blocked TCP connections, needs `local = false`.
+                    '';
+                    type = types.bool;
+                    default = !cfg.settings.io.local;
+                    defaultText = "`!config.services.opengfw.settings.io.local`";
+                    example = false;
+                  };
+                  rcvBuf = mkOption {
+                    description = "Netlink receive buffer size.";
+                    type = types.int;
+                    default = 4194304;
+                    example = 2097152;
+                  };
+                  sndBuf = mkOption {
+                    description = "Netlink send buffer size.";
+                    type = types.int;
+                    default = 4194304;
+                    example = 2097152;
+                  };
+                };
+              };
+            };
+            ruleset = mkOption {
+              description = ''
+                The path to load specific local geoip/geosite db files.
+                If not set, they will be automatically downloaded from (Loyalsoldier/v2ray-rules-dat)[https://github.com/Loyalsoldier/v2ray-rules-dat].
+              '';
+              default = { };
+              type = types.submodule {
+                options = {
+                  geoip = mkOption {
+                    description = "Path to `geoip.dat`.";
+                    default = null;
+                    type = types.nullOr types.path;
+                  };
+                  geosite = mkOption {
+                    description = "Path to `geosite.dat`.";
+                    default = null;
+                    type = types.nullOr types.path;
+                  };
+                };
+              };
+            };
+            workers = mkOption {
+              default = { };
+              description = "Worker settings.";
+              type = types.submodule {
+                options = {
+                  count = mkOption {
+                    type = types.int;
+                    description = ''
+                      Number of workers.
+                      Recommended to be no more than the number of CPU cores
+                    '';
+                    default = 4;
+                    example = 8;
+                  };
+                  queueSize = mkOption {
+                    type = types.int;
+                    description = "Worker queue size.";
+                    default = 16;
+                    example = 32;
+                  };
+                  tcpMaxBufferedPagesTotal = mkOption {
+                    type = types.int;
+                    description = ''
+                      TCP max total buffered pages.
+                    '';
+                    default = 4096;
+                    example = 8192;
+                  };
+                  tcpMaxBufferedPagesPerConn = mkOption {
+                    type = types.int;
+                    description = ''
+                      TCP max total bufferd pages per connection.
+                    '';
+                    default = 64;
+                    example = 128;
+                  };
+                  tcpTimeout = mkOption {
+                    type = types.str;
+                    description = ''
+                      How long a connection is considered dead when no data is being transferred.
+                      Dead connections are purged from TCP reassembly pools once per minute.
+                    '';
+                    default = "10m";
+                    example = "5m";
+                  };
+                  udpMaxStreams = mkOption {
+                    type = types.int;
+                    description = "UDP max streams.";
+                    default = 4096;
+                    example = 8192;
+                  };
+                };
+              };
+            };
+          };
+        }
+      );
+    };
+
+    rules = mkOption {
+      default = [ ];
+      description = ''
+        Rules passed to OpenGFW. [Example rules](https://gfw.dev/docs/rules)
+      '';
+      type = types.listOf (
+        types.submodule {
+          options = {
+            name = mkOption {
+              description = "Name of the rule.";
+              example = "block google dns";
+              type = types.singleLineStr;
+            };
+
+            action = mkOption {
+              description = ''
+                Action of the rule. [Supported actions](https://gfw.dev/docs/rules#supported-actions)
+              '';
+              default = "allow";
+              example = "block";
+              type = types.enum [
+                "allow"
+                "block"
+                "drop"
+                "modify"
+              ];
+            };
+
+            log = mkOption {
+              description = "Whether to enable logging for the rule.";
+              default = true;
+              example = false;
+              type = types.bool;
+            };
+
+            expr = mkOption {
+              description = ''
+                [Expr Language](https://expr-lang.org/docs/language-definition) expression using [analyzers](https://gfw.dev/docs/analyzers) and [functions](https://gfw.dev/docs/functions).
+              '';
+              type = types.str;
+              example = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "google.com"})'';
+            };
+
+            modifier = mkOption {
+              default = null;
+              description = ''
+                Modification of specified packets when using the `modify` action. [Available modifiers](https://github.com/apernet/OpenGFW/tree/master/modifier)
+              '';
+              type = types.nullOr (
+                types.submodule {
+                  options = {
+                    name = mkOption {
+                      description = "Name of the modifier.";
+                      type = types.singleLineStr;
+                      example = "dns";
+                    };
+
+                    args = mkOption {
+                      description = "Arguments passed to the modifier.";
+                      type = types.attrs;
+                      example = {
+                        a = "0.0.0.0";
+                        aaaa = "::";
+                      };
+                    };
+                  };
+                }
+              );
+            };
+          };
+        }
+      );
+
+      example = [
+        {
+          name = "block v2ex http";
+          action = "block";
+          expr = ''string(http?.req?.headers?.host) endsWith "v2ex.com"'';
+        }
+        {
+          name = "block google socks";
+          action = "block";
+          expr = ''string(socks?.req?.addr) endsWith "google.com" && socks?.req?.port == 80'';
+        }
+        {
+          name = "v2ex dns poisoning";
+          action = "modify";
+          modifier = {
+            name = "dns";
+            args = {
+              a = "0.0.0.0";
+              aaaa = "::";
+            };
+          };
+          expr = ''dns != nil && dns.qr && any(dns.questions, {.name endsWith "v2ex.com"})'';
+        }
+      ];
+    };
+  };
+
+  config =
+    let
+      format = pkgs.formats.yaml { };
+
+      settings =
+        if cfg.settings != null then
+          format.generate "opengfw-config.yaml" cfg.settings
+        else
+          cfg.settingsFile;
+      rules = if cfg.rules != [ ] then format.generate "opengfw-rules.yaml" cfg.rules else cfg.rulesFile;
+    in
+    mkIf cfg.enable {
+      security.wrappers.OpenGFW = {
+        owner = cfg.user;
+        group = cfg.user;
+        capabilities = "cap_net_admin+ep";
+        source = "${cfg.package}/bin/OpenGFW";
+      };
+
+      systemd.services.opengfw = {
+        description = "OpenGFW";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        path = with pkgs; [ iptables ];
+
+        preStart = ''
+          ${optionalString (rules != null) "ln -sf ${rules} rules.yaml"}
+          ${optionalString (settings != null) "ln -sf ${settings} config.yaml"}
+        '';
+
+        script = ''
+          ${config.security.wrapperDir}/OpenGFW \
+            -f ${cfg.logFormat} \
+            -l ${cfg.logLevel} \
+            ${optionalString (cfg.pcapReplay != null) "-p ${cfg.pcapReplay}"} \
+            -c config.yaml \
+            rules.yaml
+        '';
+
+        serviceConfig = rec {
+          WorkingDirectory = cfg.dir;
+          ExecReload = "kill -HUP $MAINPID";
+          Restart = "always";
+          User = cfg.user;
+          StandardOutput = mkIf (cfg.logFile != null) "append:${cfg.logFile}";
+          StandardError = StandardOutput;
+        };
+      };
+
+      users = {
+        groups.${cfg.user} = { };
+        users.${cfg.user} = {
+          description = "opengfw user";
+          isSystemUser = true;
+          group = cfg.user;
+          home = cfg.dir;
+          createHome = true;
+          homeMode = "750";
+        };
+      };
+    };
+  meta.maintainers = with lib.maintainers; [ eum3l ];
+}
diff --git a/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
index 4a00cdc649754..fba6d5e48f927 100644
--- a/nixos/modules/services/networking/openvpn.nix
+++ b/nixos/modules/services/networking/openvpn.nix
@@ -223,7 +223,7 @@ in
 
   config = mkIf (cfg.servers != { }) {
 
-    systemd.services = (listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
+    systemd.services = (listToAttrs (mapAttrsToList (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers))
       // restartService;
 
     environment.systemPackages = [ openvpn ];
diff --git a/nixos/modules/services/networking/pppd.nix b/nixos/modules/services/networking/pppd.nix
index 8310b119b5f67..3642a4b15a661 100644
--- a/nixos/modules/services/networking/pppd.nix
+++ b/nixos/modules/services/networking/pppd.nix
@@ -7,7 +7,7 @@ let
 in
 {
   meta = {
-    maintainers = with maintainers; [ ];
+    maintainers = [ ];
   };
 
   options = {
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index 762c5d8d61468..9e54dfc17dfe7 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -266,6 +266,13 @@ let
     else if builtins.isList x then "{ ${lib.concatMapStringsSep ", " toLua x} }"
     else throw "Invalid Lua value";
 
+  settingsToLua = prefix: settings: generators.toKeyValue {
+    listsAsDuplicateKeys = false;
+    mkKeyValue = k: generators.mkKeyValueDefault {
+      mkValueString = toLua;
+    } " = " (prefix + k);
+  } (filterAttrs (k: v: v != null) settings);
+
   createSSLOptsStr = o: ''
     ssl = {
       cafile = "/etc/ssl/certs/ca-bundle.crt";
@@ -418,15 +425,26 @@ let
       httpUploadPath = mkOption {
         type = types.str;
         description = ''
-          Directory where the uploaded files will be stored. By
-          default, uploaded files are put in a sub-directory of the
-          default Prosody storage path (usually /var/lib/prosody).
+          Directory where the uploaded files will be stored when the http_upload module is used.
+          By default, uploaded files are put in a sub-directory of the default Prosody storage path (usually /var/lib/prosody).
         '';
         default = "/var/lib/prosody";
       };
     };
   };
 
+  httpFileShareOpts = { ... }: {
+    freeformType = with types;
+      let atom = oneOf [ int bool str (listOf atom) ]; in
+      attrsOf (nullOr atom) // {
+        description = "int, bool, string or list of them";
+      };
+    options.domain = mkOption {
+      type = with types; nullOr str;
+      description = "Domain name for a http_file_share service.";
+    };
+  };
+
   vHostOpts = { ... }: {
 
     options = {
@@ -650,7 +668,7 @@ in
 
       uploadHttp = mkOption {
         description = ''
-          Configures the Prosody builtin HTTP server to handle user uploads.
+          Configures the old Prosody builtin HTTP server to handle user uploads.
         '';
         type = types.nullOr (types.submodule uploadHttpOpts);
         default = null;
@@ -659,6 +677,17 @@ in
         };
       };
 
+      httpFileShare = mkOption {
+        description = ''
+          Configures the http_file_share module to handle user uploads.
+        '';
+        type = types.nullOr (types.submodule httpFileShareOpts);
+        default = null;
+        example = {
+          domain = "uploads.my-xmpp-example-host.org";
+        };
+      };
+
       muc = mkOption {
         type = types.listOf (types.submodule mucOpts);
         default = [ ];
@@ -751,11 +780,10 @@ in
             You need to setup at least a MUC domain to comply with
             XEP-0423.
           '' + genericErrMsg;}
-        { assertion = cfg.uploadHttp != null || !cfg.xmppComplianceSuite;
+        { assertion = cfg.uploadHttp != null || cfg.httpFileShare != null || !cfg.xmppComplianceSuite;
           message = ''
-            You need to setup the uploadHttp module through
-            config.services.prosody.uploadHttp to comply with
-            XEP-0423.
+            You need to setup the http_upload or http_file_share modules through config.services.prosody.uploadHttp
+            or config.services.prosody.httpFileShare to comply with XEP-0423.
           '' + genericErrMsg;}
       ];
     in errors;
@@ -764,8 +792,11 @@ in
 
     environment.etc."prosody/prosody.cfg.lua".text =
       let
-        httpDiscoItems = optionals (cfg.uploadHttp != null)
-            [{ url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";}];
+        httpDiscoItems = optional (cfg.uploadHttp != null) {
+          url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";
+        } ++ optional (cfg.httpFileShare != null) {
+          url = cfg.httpFileShare.domain; description = "HTTP file share endpoint";
+        };
         mucDiscoItems = builtins.foldl'
             (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc)
             []
@@ -844,7 +875,6 @@ in
         '') cfg.muc}
 
       ${ lib.optionalString (cfg.uploadHttp != null) ''
-        -- TODO: think about migrating this to mod-http_file_share instead.
         Component ${toLua cfg.uploadHttp.domain} "http_upload"
             http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
             http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
@@ -852,6 +882,11 @@ in
             http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath}
       ''}
 
+      ${lib.optionalString (cfg.httpFileShare != null) ''
+        Component ${toLua cfg.httpFileShare.domain} "http_file_share"
+        ${settingsToLua "  http_file_share_" (cfg.httpFileShare // { domain = null; })}
+      ''}
+
       ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
         VirtualHost "${v.domain}"
           enabled = ${boolToString v.enabled};
diff --git a/nixos/modules/services/networking/rathole.nix b/nixos/modules/services/networking/rathole.nix
new file mode 100644
index 0000000000000..b6cd3ff89d9cf
--- /dev/null
+++ b/nixos/modules/services/networking/rathole.nix
@@ -0,0 +1,165 @@
+{
+  pkgs,
+  lib,
+  config,
+  ...
+}:
+
+let
+  cfg = config.services.rathole;
+  settingsFormat = pkgs.formats.toml { };
+  py-toml-merge =
+    pkgs.writers.writePython3Bin "py-toml-merge"
+      {
+        libraries = with pkgs.python3Packages; [
+          tomli-w
+          mergedeep
+        ];
+      }
+      ''
+        import argparse
+        from pathlib import Path
+        from typing import Any
+
+        import tomli_w
+        import tomllib
+        from mergedeep import merge
+
+        parser = argparse.ArgumentParser(description="Merge multiple TOML files")
+        parser.add_argument(
+            "files",
+            type=Path,
+            nargs="+",
+            help="List of TOML files to merge",
+        )
+
+        args = parser.parse_args()
+        merged: dict[str, Any] = {}
+
+        for file in args.files:
+            with open(file, "rb") as fh:
+                loaded_toml = tomllib.load(fh)
+                merged = merge(merged, loaded_toml)
+
+        print(tomli_w.dumps(merged))
+      '';
+in
+
+{
+  options = {
+    services.rathole = {
+      enable = lib.mkEnableOption "Rathole";
+
+      package = lib.mkPackageOption pkgs "rathole" { };
+
+      role = lib.mkOption {
+        type = lib.types.enum [
+          "server"
+          "client"
+        ];
+        description = ''
+          Select whether rathole needs to be run as a `client` or a `server`.
+          Server is a machine with a public IP and client is a device behind NAT,
+          but running some services that need to be exposed to the Internet.
+        '';
+      };
+
+      credentialsFile = lib.mkOption {
+        type = lib.types.path;
+        default = "/dev/null";
+        description = ''
+          Path to a TOML file to be merged with the settings.
+          Useful to set secret config parameters like tokens, which
+          should not appear in the Nix Store.
+        '';
+        example = "/var/lib/secrets/rathole/config.toml";
+      };
+
+      settings = lib.mkOption {
+        type = settingsFormat.type;
+        default = { };
+        description = ''
+          Rathole configuration, for options reference
+          see the [example](https://github.com/rapiz1/rathole?tab=readme-ov-file#configuration) on GitHub.
+          Both server and client configurations can be specified at the same time, regardless of the selected role.
+        '';
+        example = {
+          server = {
+            bind_addr = "0.0.0.0:2333";
+            services.my_nas_ssh = {
+              token = "use_a_secret_that_only_you_know";
+              bind_addr = "0.0.0.0:5202";
+            };
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.rathole = {
+      requires = [ "network.target" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      description = "Rathole ${cfg.role} Service";
+
+      serviceConfig =
+        let
+          name = "rathole";
+          configFile = settingsFormat.generate "${name}.toml" cfg.settings;
+          runtimeDir = "/run/${name}";
+          ratholePrestart =
+            "+"
+            + (pkgs.writeShellScript "rathole-prestart" ''
+              DYNUSER_UID=$(stat -c %u ${runtimeDir})
+              DYNUSER_GID=$(stat -c %g ${runtimeDir})
+              ${lib.getExe py-toml-merge} ${configFile} '${cfg.credentialsFile}' |
+                install -m 600 -o $DYNUSER_UID -g $DYNUSER_GID /dev/stdin ${runtimeDir}/${mergedConfigName}
+            '');
+          mergedConfigName = "merged.toml";
+        in
+        {
+          Type = "simple";
+          Restart = "on-failure";
+          RestartSec = 5;
+          ExecStartPre = ratholePrestart;
+          ExecStart = "${lib.getExe cfg.package} --${cfg.role} ${runtimeDir}/${mergedConfigName}";
+          DynamicUser = true;
+          LimitNOFILE = "1048576";
+          RuntimeDirectory = name;
+          RuntimeDirectoryMode = "0700";
+          # Hardening
+          AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          PrivateDevices = true;
+          PrivateMounts = true;
+          PrivateTmp = true;
+          # PrivateUsers=true breaks AmbientCapabilities=CAP_NET_BIND_SERVICE
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SystemCallArchitectures = "native";
+          UMask = "0066";
+        };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ xokdvium ];
+}
diff --git a/nixos/modules/services/networking/realm.nix b/nixos/modules/services/networking/realm.nix
new file mode 100644
index 0000000000000..5b0c34ac825ff
--- /dev/null
+++ b/nixos/modules/services/networking/realm.nix
@@ -0,0 +1,50 @@
+{ config
+, lib
+, pkgs
+, ...
+}:
+let
+  cfg = config.services.realm;
+  configFormat = pkgs.formats.json { };
+  configFile = configFormat.generate "config.json" cfg.config;
+  inherit (lib)
+    mkEnableOption mkPackageOption mkOption mkIf types getExe;
+in
+{
+
+  meta.maintainers = with lib.maintainers; [ ocfox ];
+
+  options = {
+    services.realm = {
+      enable = mkEnableOption "A simple, high performance relay server written in rust";
+      package = mkPackageOption pkgs "realm" { };
+      config = mkOption {
+        type = types.submodule {
+          freeformType = configFormat.type;
+        };
+        default = { };
+        description = ''
+          The realm configuration, see <https://github.com/zhboner/realm#overview> for documentation.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.realm = {
+      serviceConfig = {
+        DynamicUser = true;
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        ProtectClock = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectProc = "invisible";
+        ProtectKernelTunables = true;
+        ExecStart = "${getExe cfg.package} --config ${configFile}";
+        AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_BIND_SERVICE" ];
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix
index c788069fbaa55..02773d78b1328 100644
--- a/nixos/modules/services/networking/resilio.nix
+++ b/nixos/modules/services/networking/resilio.nix
@@ -291,5 +291,5 @@ in
     };
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/networking/scion/scion-control.nix b/nixos/modules/services/networking/scion/scion-control.nix
index 95d78a87ac859..8b6733ac0c244 100644
--- a/nixos/modules/services/networking/scion/scion-control.nix
+++ b/nixos/modules/services/networking/scion/scion-control.nix
@@ -3,8 +3,10 @@
 with lib;
 
 let
+  globalCfg = config.services.scion;
   cfg = config.services.scion.scion-control;
   toml = pkgs.formats.toml { };
+  connectionDir = if globalCfg.stateless then "/run" else "/var/lib";
   defaultConfig = {
     general = {
       id = "cs";
@@ -12,13 +14,13 @@ let
       reconnect_to_dispatcher = true;
     };
     beacon_db = {
-      connection = "/run/scion-control/control.beacon.db";
+      connection = "${connectionDir}/scion-control/control.beacon.db";
     };
     path_db = {
-      connection = "/run/scion-control/control.path.db";
+      connection = "${connectionDir}/scion-control/control.path.db";
     };
     trust_db = {
-      connection = "/run/scion-control/control.trust.db";
+      connection = "${connectionDir}/scion-control/control.trust.db";
     };
     log.console = {
       level = "info";
@@ -58,11 +60,11 @@ in
       serviceConfig = {
         Type = "simple";
         Group = if (config.services.scion.scion-dispatcher.enable == true) then "scion" else null;
-        ExecStart = "${pkgs.scion}/bin/scion-control --config ${configFile}";
+        ExecStart = "${globalCfg.package}/bin/scion-control --config ${configFile}";
         DynamicUser = true;
         Restart = "on-failure";
         BindPaths = [ "/dev/shm:/run/shm" ];
-        RuntimeDirectory = "scion-control";
+        ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-control";
       };
     };
   };
diff --git a/nixos/modules/services/networking/scion/scion-daemon.nix b/nixos/modules/services/networking/scion/scion-daemon.nix
index 8528bec1d52eb..09416178d35ab 100644
--- a/nixos/modules/services/networking/scion/scion-daemon.nix
+++ b/nixos/modules/services/networking/scion/scion-daemon.nix
@@ -3,8 +3,10 @@
 with lib;
 
 let
+  globalCfg = config.services.scion;
   cfg = config.services.scion.scion-daemon;
   toml = pkgs.formats.toml { };
+  connectionDir = if globalCfg.stateless then "/run" else "/var/lib";
   defaultConfig = {
     general = {
       id = "sd";
@@ -12,10 +14,10 @@ let
       reconnect_to_dispatcher = true;
     };
     path_db = {
-      connection = "/run/scion-daemon/sd.path.db";
+      connection = "${connectionDir}/scion-daemon/sd.path.db";
     };
     trust_db = {
-      connection = "/run/scion-daemon/sd.trust.db";
+      connection = "${connectionDir}/scion-daemon/sd.trust.db";
     };
     log.console = {
       level = "info";
@@ -54,10 +56,10 @@ in
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${pkgs.scion}/bin/scion-daemon --config ${configFile}";
+        ExecStart = "${globalCfg.package}/bin/scion-daemon --config ${configFile}";
         Restart = "on-failure";
         DynamicUser = true;
-        RuntimeDirectory = "scion-daemon";
+        ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-daemon";
       };
     };
   };
diff --git a/nixos/modules/services/networking/scion/scion-dispatcher.nix b/nixos/modules/services/networking/scion/scion-dispatcher.nix
index 7c9f5e6a385ee..7d2e28be75f72 100644
--- a/nixos/modules/services/networking/scion/scion-dispatcher.nix
+++ b/nixos/modules/services/networking/scion/scion-dispatcher.nix
@@ -3,6 +3,7 @@
 with lib;
 
 let
+  globalCfg = config.services.scion;
   cfg = config.services.scion.scion-dispatcher;
   toml = pkgs.formats.toml { };
   defaultConfig = {
@@ -64,9 +65,9 @@ in
         DynamicUser = true;
         BindPaths = [ "/dev/shm:/run/shm" ];
         ExecStartPre = "${pkgs.coreutils}/bin/rm -rf /run/shm/dispatcher";
-        ExecStart = "${pkgs.scion}/bin/scion-dispatcher --config ${configFile}";
+        ExecStart = "${globalCfg.package}/bin/scion-dispatcher --config ${configFile}";
         Restart = "on-failure";
-        RuntimeDirectory = "scion-dispatcher";
+        ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-dispatcher";
       };
     };
   };
diff --git a/nixos/modules/services/networking/scion/scion-router.nix b/nixos/modules/services/networking/scion/scion-router.nix
index 2cac44ab767ef..7587a06ed9dbe 100644
--- a/nixos/modules/services/networking/scion/scion-router.nix
+++ b/nixos/modules/services/networking/scion/scion-router.nix
@@ -3,6 +3,7 @@
 with lib;
 
 let
+  globalCfg = config.services.scion;
   cfg = config.services.scion.scion-router;
   toml = pkgs.formats.toml { };
   defaultConfig = {
@@ -39,10 +40,10 @@ in
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${pkgs.scion}/bin/scion-router --config ${configFile}";
+        ExecStart = "${globalCfg.package}/bin/scion-router --config ${configFile}";
         Restart = "on-failure";
         DynamicUser = true;
-        RuntimeDirectory = "scion-router";
+        ${if globalCfg.stateless then "RuntimeDirectory" else "StateDirectory"} = "scion-router";
       };
     };
   };
diff --git a/nixos/modules/services/networking/scion/scion.nix b/nixos/modules/services/networking/scion/scion.nix
index b8bfef8b93b58..efab97f7cd8da 100644
--- a/nixos/modules/services/networking/scion/scion.nix
+++ b/nixos/modules/services/networking/scion/scion.nix
@@ -8,6 +8,23 @@ in
 {
   options.services.scion = {
     enable = mkEnableOption "all of the scion components and services";
+    package = mkPackageOption pkgs "scion" { };
+    stateless = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Setting this value to false (stateful) can lead to improved caching and
+        performance.
+
+        This option decides whether to persist the SCION path sqlite databases
+        on disk or not. Persisting this data can lead to database corruption in
+        extreme cases such as power outage, meaning SCION fails to work on the
+        next boot. This is being investigated.
+
+        If true, /run/scion-* is used for data
+        If false, use /var/lib/scion-* is used for data
+      '';
+    };
     bypassBootstrapWarning = mkOption {
       type = types.bool;
       default = false;
@@ -18,7 +35,7 @@ in
   };
   config = mkIf cfg.enable {
     environment.systemPackages = [
-      pkgs.scion
+      cfg.package
     ];
     services.scion = {
       scion-dispatcher.enable = true;
diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index a07cde847cf69..2e572a3d071af 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -337,7 +337,7 @@ in
     };
 
     # use nginx to serve the smokeping web service
-    services.fcgiwrap.smokeping = mkIf cfg.webService {
+    services.fcgiwrap.instances.smokeping = mkIf cfg.webService {
       process.user = cfg.user;
       process.group = cfg.user;
       socket = { inherit (config.services.nginx) user group; };
@@ -353,7 +353,7 @@ in
         locations."/smokeping.fcgi" = {
           extraConfig = ''
             include ${config.services.nginx.package}/conf/fastcgi_params;
-            fastcgi_pass unix:${config.services.fcgiwrap.smokeping.socket.address};
+            fastcgi_pass unix:${config.services.fcgiwrap.instances.smokeping.socket.address};
             fastcgi_param SCRIPT_FILENAME ${smokepingHome}/smokeping.fcgi;
             fastcgi_param DOCUMENT_ROOT ${smokepingHome};
           '';
diff --git a/nixos/modules/services/networking/spiped.nix b/nixos/modules/services/networking/spiped.nix
index ada36ee9be0bc..a5188d933995e 100644
--- a/nixos/modules/services/networking/spiped.nix
+++ b/nixos/modules/services/networking/spiped.nix
@@ -62,11 +62,11 @@ in
               keyfile = mkOption {
                 type    = types.path;
                 description = ''
-                  Name of a file containing the spiped key. As the
-                  daemon runs as the `spiped` user, the
-                  key file must be somewhere owned by that user. By
-                  default, we recommend putting the keys for any spipe
-                  services in `/var/lib/spiped`.
+                  Name of a file containing the spiped key.
+                  As the daemon runs as the `spiped` user,
+                  the key file must be readable by that user.
+                  To securely manage the file within your configuration
+                  consider a tool such as agenix or sops-nix.
                 '';
               };
 
@@ -185,22 +185,12 @@ in
       serviceConfig = {
         Restart   = "always";
         User      = "spiped";
-        PermissionsStartOnly = true;
       };
 
-      preStart  = ''
-        cd /var/lib/spiped
-        chmod -R 0660 *
-        chown -R spiped:spiped *
-      '';
       scriptArgs = "%i";
       script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`";
     };
 
-    systemd.tmpfiles.rules = lib.mkIf (cfg.config != { }) [
-      "d /var/lib/spiped -"
-    ];
-
     # Setup spiped config files
     environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec"
       { text = concatStringsSep " "
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
index 2062f2806d79f..369c5a9397659 100644
--- a/nixos/modules/services/networking/wg-quick.nix
+++ b/nixos/modules/services/networking/wg-quick.nix
@@ -57,6 +57,15 @@ let
         '';
       };
 
+      generatePrivateKeyFile = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Automatically generate a private key with
+          {command}`wg genkey`, at the privateKeyFile location.
+        '';
+      };
+
       privateKeyFile = mkOption {
         example = "/private/wireguard_key";
         type = with types; nullOr str;
@@ -218,9 +227,24 @@ let
 
   writeScriptFile = name: text: ((pkgs.writeShellScriptBin name text) + "/bin/${name}");
 
+  generatePrivateKeyScript = privateKeyFile: ''
+    set -e
+
+    # If the parent dir does not already exist, create it.
+    # Otherwise, does nothing, keeping existing permissions intact.
+    mkdir -p --mode 0755 "${dirOf privateKeyFile}"
+
+    if [ ! -f "${privateKeyFile}" ]; then
+      # Write private key file with atomically-correct permissions.
+      (set -e; umask 077; wg genkey > "${privateKeyFile}")
+    fi
+  '';
+
   generateUnit = name: values:
     assert assertMsg (values.configFile != null || ((values.privateKey != null) != (values.privateKeyFile != null))) "Only one of privateKey, configFile or privateKeyFile may be set";
+    assert assertMsg (values.generatePrivateKeyFile == false || values.privateKeyFile != null) "generatePrivateKeyFile requires privateKeyFile to be set";
     let
+      generateKeyScriptFile = if values.generatePrivateKeyFile then writeScriptFile "generatePrivateKey.sh" (generatePrivateKeyScript values.privateKeyFile) else null;
       preUpFile = if values.preUp != "" then writeScriptFile "preUp.sh" values.preUp else null;
       postUp =
             optional (values.privateKeyFile != null) "wg set ${name} private-key <(cat ${values.privateKeyFile})" ++
@@ -247,6 +271,7 @@ let
         optionalString (values.mtu != null) "MTU = ${toString values.mtu}\n" +
         optionalString (values.privateKey != null) "PrivateKey = ${values.privateKey}\n" +
         optionalString (values.listenPort != null) "ListenPort = ${toString values.listenPort}\n" +
+        optionalString (generateKeyScriptFile != null) "PreUp = ${generateKeyScriptFile}\n" +
         optionalString (preUpFile != null) "PreUp = ${preUpFile}\n" +
         optionalString (postUpFile != null) "PostUp = ${postUpFile}\n" +
         optionalString (preDownFile != null) "PreDown = ${preDownFile}\n" +
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 81abae2c9303d..08e5494b63df9 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -481,29 +481,27 @@ let
           RemainAfterExit = true;
         };
 
-        script = ''
-          ${optionalString (!config.boot.isContainer) "modprobe wireguard || true"}
-
-          ${values.preSetup}
-
-          ${ipPreMove} link add dev "${name}" type wireguard
-          ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''}
-          ${optionalString (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''}
-
-          ${concatMapStringsSep "\n" (ip:
+        script = concatStringsSep "\n" (
+          optional (!config.boot.isContainer) "modprobe wireguard || true"
+          ++ [
+            values.preSetup
+            ''${ipPreMove} link add dev "${name}" type wireguard''
+          ]
+          ++ optional (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''
+          ++ optional (values.mtu != null) ''${ipPostMove} link set "${name}" mtu ${toString values.mtu}''
+          ++ (map (ip:
             ''${ipPostMove} address add "${ip}" dev "${name}"''
-          ) values.ips}
-
-          ${concatStringsSep " " (
+          ) values.ips)
+          ++ [
+            (concatStringsSep " " (
             [ ''${wg} set "${name}" private-key "${privKey}"'' ]
             ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"''
             ++ optional (values.fwMark != null) ''fwmark "${values.fwMark}"''
-          )}
-
-          ${ipPostMove} link set up dev "${name}"
-
-          ${values.postSetup}
-        '';
+            ))
+            ''${ipPostMove} link set up dev "${name}"''
+            values.postSetup
+          ]
+          );
 
         postStop = ''
           ${values.preShutdown}
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 435cd530c18d4..b068f22273102 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -3,10 +3,6 @@
 with lib;
 
 let
-  package = if cfg.allowAuxiliaryImperativeNetworks
-    then pkgs.wpa_supplicant_ro_ssids
-    else pkgs.wpa_supplicant;
-
   cfg = config.networking.wireless;
   opt = options.networking.wireless;
 
@@ -106,7 +102,7 @@ let
       wantedBy = [ "multi-user.target" ];
       stopIfChanged = false;
 
-      path = [ package ];
+      path = [ pkgs.wpa_supplicant ];
       # if `userControl.enable`, the supplicant automatically changes the permissions
       #  and owning group of the runtime dir; setting `umask` ensures the generated
       #  config file isn't readable (except to root);  see nixpkgs#267693
@@ -139,9 +135,9 @@ let
                 $0 = substr($0, 1, i-1) repl substr($0, i+length(find))
             }
             print
-          }' "${configFile}" > "${finalConfig}"
+          }' "${configFile}" > ${finalConfig}
         else
-          touch "${finalConfig}"
+          touch ${finalConfig}
         fi
 
         iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"
@@ -521,8 +517,8 @@ in {
 
     hardware.wirelessRegulatoryDatabase = true;
 
-    environment.systemPackages = [ package ];
-    services.dbus.packages = optional cfg.dbusControlled package;
+    environment.systemPackages = [ pkgs.wpa_supplicant ];
+    services.dbus.packages = optional cfg.dbusControlled pkgs.wpa_supplicant;
 
     systemd.services =
       if cfg.interfaces == []
diff --git a/nixos/modules/services/networking/wstunnel.nix b/nixos/modules/services/networking/wstunnel.nix
index bd7536351955a..439e1f8ea9b5d 100644
--- a/nixos/modules/services/networking/wstunnel.nix
+++ b/nixos/modules/services/networking/wstunnel.nix
@@ -23,14 +23,14 @@ let
   };
 
   commonOptions = {
-    enable = lib.mkEnableOption "this `wstunnel` instance." // {
+    enable = lib.mkEnableOption "this `wstunnel` instance" // {
       default = true;
     };
 
     package = lib.mkPackageOption pkgs "wstunnel" { };
 
     autoStart =
-      lib.mkEnableOption "starting this wstunnel instance automatically." // {
+      lib.mkEnableOption "starting this wstunnel instance automatically" // {
         default = true;
       };
 
diff --git a/nixos/modules/services/networking/wvdial.nix b/nixos/modules/services/networking/wvdial.nix
new file mode 100644
index 0000000000000..8e06d64940d01
--- /dev/null
+++ b/nixos/modules/services/networking/wvdial.nix
@@ -0,0 +1,47 @@
+# Global configuration for wvdial.
+
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.environment.wvdial;
+in
+{
+  options = {
+    environment.wvdial = {
+      dialerDefaults = lib.mkOption {
+        default = "";
+        type = lib.types.str;
+        example = ''Init1 = AT+CGDCONT=1,"IP","internet.t-mobile"'';
+        description = ''
+          Contents of the "Dialer Defaults" section of
+          <filename>/etc/wvdial.conf</filename>.
+        '';
+      };
+      pppDefaults = lib.mkOption {
+        default = ''
+          noipdefault
+          usepeerdns
+          defaultroute
+          persist
+          noauth
+        '';
+        type = lib.types.str;
+        description = "Default ppp settings for wvdial.";
+      };
+    };
+  };
+
+  config = lib.mkIf (cfg.dialerDefaults != "") {
+    environment.etc."wvdial.conf".source = pkgs.writeText "wvdial.conf" ''
+      [Dialer Defaults]
+      PPPD PATH = ${pkgs.ppp}/sbin/pppd
+      ${config.environment.wvdial.dialerDefaults}
+    '';
+    environment.etc."ppp/peers/wvdial".source = pkgs.writeText "wvdial" cfg.pppDefaults;
+  };
+}
diff --git a/nixos/modules/services/networking/zeronsd.nix b/nixos/modules/services/networking/zeronsd.nix
new file mode 100644
index 0000000000000..23f1a5fa7e4f8
--- /dev/null
+++ b/nixos/modules/services/networking/zeronsd.nix
@@ -0,0 +1,117 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.zeronsd;
+  settingsFormat = pkgs.formats.json { };
+in
+{
+  options.services.zeronsd.servedNetworks = lib.mkOption {
+    default = { };
+    example = {
+      "a8a2c3c10c1a68de".settings.token = "/var/lib/zeronsd/apitoken";
+    };
+    description = "ZeroTier Networks to start zeronsd instances for.";
+    type = lib.types.attrsOf (
+      lib.types.submodule {
+        options = {
+          package = lib.mkPackageOption pkgs "zeronsd" { };
+
+          settings = lib.mkOption {
+            description = "Settings for zeronsd";
+            default = { };
+            type = lib.types.submodule {
+              freeformType = settingsFormat.type;
+
+              options = {
+                domain = lib.mkOption {
+                  default = "home.arpa";
+                  type = lib.types.singleLineStr;
+                  description = "Domain under which ZeroTier records will be available.";
+                };
+
+                token = lib.mkOption {
+                  type = lib.types.path;
+                  description = "Path to a file containing the API Token for ZeroTier Central.";
+                };
+
+                log_level = lib.mkOption {
+                  default = "warn";
+                  type = lib.types.enum [
+                    "off"
+                    "error"
+                    "warn"
+                    "info"
+                    "debug"
+                    "trace"
+                  ];
+                  description = "Log Level.";
+                };
+
+                wildcard = lib.mkOption {
+                  default = false;
+                  type = lib.types.bool;
+                  description = "Whether to serve a wildcard record for ZeroTier Nodes.";
+                };
+              };
+            };
+          };
+        };
+      }
+    );
+  };
+
+  config = lib.mkIf (cfg.servedNetworks != { }) {
+    assertions = [
+      {
+        assertion = config.services.zerotierone.enable;
+        message = "zeronsd needs a configured zerotier-one";
+      }
+    ];
+
+    systemd.services = lib.mapAttrs' (netname: netcfg: {
+      name = "zeronsd-${netname}";
+      value = {
+        description = "ZeroTier DNS server for Network ${netname}";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [
+          "network.target"
+          "zerotierone.service"
+        ];
+        wants = [ "network-online.target" ];
+
+        serviceConfig =
+          let
+            configFile = pkgs.writeText "zeronsd.json" (builtins.toJSON netcfg.settings);
+          in
+          {
+            ExecStart = "${netcfg.package}/bin/zeronsd start --config ${configFile} --config-type json ${netname}";
+            Restart = "on-failure";
+            RestartSec = 2;
+            TimeoutStopSec = 5;
+            User = "zeronsd";
+            Group = "zeronsd";
+            AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+          };
+      };
+    }) cfg.servedNetworks;
+
+    systemd.tmpfiles.rules = [
+      "a+ /var/lib/zerotier-one - - - - u:zeronsd:x"
+      "a+ /var/lib/zerotier-one/authtoken.secret - - - - mask::r,u:zeronsd:r"
+    ];
+
+    users.users.zeronsd = {
+      group = "zeronsd";
+      description = "Service user for running zeronsd";
+      isSystemUser = true;
+    };
+
+    users.groups.zeronsd = { };
+  };
+}
diff --git a/nixos/modules/services/search/tika.nix b/nixos/modules/services/search/tika.nix
new file mode 100644
index 0000000000000..94096b6db29fe
--- /dev/null
+++ b/nixos/modules/services/search/tika.nix
@@ -0,0 +1,98 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.tika;
+  inherit (lib)
+    literalExpression
+    mkIf
+    mkEnableOption
+    mkOption
+    mkPackageOption
+    getExe
+    types
+    ;
+in
+{
+  meta.maintainers = [ lib.maintainers.drupol ];
+
+  options = {
+    services.tika = {
+      enable = mkEnableOption "Apache Tika server";
+      package = mkPackageOption pkgs "tika" { };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        example = "0.0.0.0";
+        description = ''
+          The Apache Tika bind address.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 9998;
+        description = ''
+          The Apache Tike port to listen on
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The Apache Tika configuration (XML) file to use.
+        '';
+        example = literalExpression "./tika/tika-config.xml";
+      };
+
+      enableOcr = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable OCR support by adding the `tesseract` package as a dependency.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open the firewall for Apache Tika.
+          This adds `services.tika.port` to `networking.firewall.allowedTCPPorts`.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.tika = {
+      description = "Apache Tika Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig =
+        let
+          package = cfg.package.override { inherit (cfg) enableOcr; };
+        in
+        {
+          Type = "simple";
+
+          ExecStart = "${getExe package} --host ${cfg.listenAddress} --port ${toString cfg.port} ${
+            lib.optionalString (cfg.configFile != null) "--config ${cfg.configFile}"
+          }";
+          DynamicUser = true;
+          StateDirectory = "tika";
+          CacheDirectory = "tika";
+        };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.port ]; };
+  };
+}
diff --git a/nixos/modules/services/security/authelia.nix b/nixos/modules/services/security/authelia.nix
index cf1c57e34c4ef..f73c1ce41e5b9 100644
--- a/nixos/modules/services/security/authelia.nix
+++ b/nixos/modules/services/security/authelia.nix
@@ -8,7 +8,6 @@ let
   cfg = config.services.authelia;
 
   format = pkgs.formats.yaml { };
-  configFile = format.generate "config.yml" cfg.settings;
 
   autheliaOpts = with lib; { name, ... }: {
     options = {
@@ -156,18 +155,12 @@ let
             };
 
             server = {
-              host = mkOption {
+              address = mkOption {
                 type = types.str;
-                default = "localhost";
-                example = "0.0.0.0";
+                default = "tcp://:9091/";
+                example = "unix:///var/run/authelia.sock?path=authelia&umask=0117";
                 description = "The address to listen on.";
               };
-
-              port = mkOption {
-                type = types.port;
-                default = 9091;
-                description = "The port to listen on.";
-              };
             };
 
             log = {
@@ -233,6 +226,23 @@ let
       };
     };
   };
+
+  writeOidcJwksConfigFile = oidcIssuerPrivateKeyFile: pkgs.writeText "oidc-jwks.yaml" ''
+    identity_providers:
+      oidc:
+        jwks:
+          - key: {{ secret "${oidcIssuerPrivateKeyFile}" | mindent 10 "|" | msquote }}
+  '';
+
+  # Remove an attribute in a nested set
+  # https://discourse.nixos.org/t/modify-an-attrset-in-nix/29919/5
+  removeAttrByPath = set: pathList:
+    lib.updateManyAttrsByPath [{
+      path = lib.init pathList;
+      update = old:
+        lib.filterAttrs (n: v: n != (lib.last pathList)) old;
+    }]
+      set;
 in
 {
   options.services.authelia.instances = with lib; mkOption {
@@ -281,9 +291,19 @@ in
     let
       mkInstanceServiceConfig = instance:
         let
+          cleanedSettings =
+            if (instance.settings.server?host || instance.settings.server?port || instance.settings.server?path) then
+            # Old settings are used: display a warning and remove the default value of server.address
+            # as authelia does not allow both old and new settings to be set
+              lib.warn "Please replace services.authelia.instances.${instance.name}.settings.{host,port,path} with services.authelia.instances.${instance.name}.settings.address, before release 5.0.0"
+                (removeAttrByPath instance.settings [ "server" "address" ])
+            else
+              instance.settings;
+
           execCommand = "${instance.package}/bin/authelia";
-          configFile = format.generate "config.yml" instance.settings;
-          configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles])}";
+          configFile = format.generate "config.yml" cleanedSettings;
+          oidcJwksConfigFile = lib.optional (instance.secrets.oidcIssuerPrivateKeyFile != null) (writeOidcJwksConfigFile instance.secrets.oidcIssuerPrivateKeyFile);
+          configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles oidcJwksConfigFile])}";
         in
         {
           description = "Authelia authentication and authorization server";
@@ -291,10 +311,10 @@ in
           after = [ "network.target" ];
           environment =
             (lib.filterAttrs (_: v: v != null) {
-              AUTHELIA_JWT_SECRET_FILE = instance.secrets.jwtSecretFile;
+              X_AUTHELIA_CONFIG_FILTERS = lib.mkIf (oidcJwksConfigFile != [ ]) "template";
+              AUTHELIA_IDENTITY_VALIDATION_RESET_PASSWORD_JWT_SECRET_FILE = instance.secrets.jwtSecretFile;
               AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = instance.secrets.storageEncryptionKeyFile;
               AUTHELIA_SESSION_SECRET_FILE = instance.secrets.sessionSecretFile;
-              AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = instance.secrets.oidcIssuerPrivateKeyFile;
               AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = instance.secrets.oidcHmacSecretFile;
             })
             // instance.environmentVariables;
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index b3598606d8be7..a23c19f685d2d 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -5,7 +5,6 @@ let
   stateDir = "/var/lib/clamav";
   clamavGroup = clamavUser;
   cfg = config.services.clamav;
-  pkg = pkgs.clamav;
 
   toKeyValue = generators.toKeyValue {
     mkKeyValue = generators.mkKeyValueDefault { } " ";
@@ -27,6 +26,7 @@ in
 
   options = {
     services.clamav = {
+      package = mkPackageOption pkgs "clamav" { };
       daemon = {
         enable = mkEnableOption "ClamAV clamd daemon";
 
@@ -125,7 +125,7 @@ in
   };
 
   config = mkIf (cfg.updater.enable || cfg.daemon.enable) {
-    environment.systemPackages = [ pkg ];
+    environment.systemPackages = [ cfg.package ];
 
     users.users.${clamavUser} = {
       uid = config.ids.uids.clamav;
@@ -172,7 +172,7 @@ in
       restartTriggers = [ clamdConfigFile ];
 
       serviceConfig = {
-        ExecStart = "${pkg}/bin/clamd";
+        ExecStart = "${cfg.package}/bin/clamd";
         ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
         User = clamavUser;
         Group = clamavGroup;
@@ -201,7 +201,7 @@ in
 
       serviceConfig = {
         Type = "oneshot";
-        ExecStart = "${pkg}/bin/freshclam";
+        ExecStart = "${cfg.package}/bin/freshclam";
         SuccessExitStatus = "1"; # if databases are up to date
         StateDirectory = "clamav";
         User = clamavUser;
@@ -274,7 +274,7 @@ in
 
       serviceConfig = {
         Type = "oneshot";
-        ExecStart = "${pkg}/bin/clamdscan --multiscan --fdpass --infected --allmatch ${lib.concatStringsSep " " cfg.scanner.scanDirectories}";
+        ExecStart = "${cfg.package}/bin/clamdscan --multiscan --fdpass --infected --allmatch ${lib.concatStringsSep " " cfg.scanner.scanDirectories}";
       };
     };
   };
diff --git a/nixos/modules/services/security/hologram-agent.nix b/nixos/modules/services/security/hologram-agent.nix
index e29267e50003b..a2cf9611d677b 100644
--- a/nixos/modules/services/security/hologram-agent.nix
+++ b/nixos/modules/services/security/hologram-agent.nix
@@ -54,5 +54,5 @@ in {
 
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/security/shibboleth-sp.nix b/nixos/modules/services/security/shibboleth-sp.nix
index c6d260b902670..0a85173501d5f 100644
--- a/nixos/modules/services/security/shibboleth-sp.nix
+++ b/nixos/modules/services/security/shibboleth-sp.nix
@@ -70,5 +70,5 @@ in {
     };
   };
 
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 }
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 520da45c94e2f..20d2dadbb7e22 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -10,7 +10,7 @@ let
   '';
 
 in {
-  meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
+  meta.maintainers = with maintainers; [ calbrecht jcumming ];
 
   options = {
 
diff --git a/nixos/modules/services/security/step-ca.nix b/nixos/modules/services/security/step-ca.nix
index 43bc402e7818b..70651e995dda1 100644
--- a/nixos/modules/services/security/step-ca.nix
+++ b/nixos/modules/services/security/step-ca.nix
@@ -4,7 +4,7 @@ let
   settingsFormat = (pkgs.formats.json { });
 in
 {
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 
   options = {
     services.step-ca = {
diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix
index 41f7de5d80fab..11d49b580b310 100644
--- a/nixos/modules/services/security/vaultwarden/default.nix
+++ b/nixos/modules/services/security/vaultwarden/default.nix
@@ -7,6 +7,8 @@ let
 
   StateDirectory = if lib.versionOlder config.system.stateVersion "24.11" then "bitwarden_rs" else "vaultwarden";
 
+  dataDir = "/var/lib/${StateDirectory}";
+
   # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
   nameToEnvVar = name:
     let
@@ -25,7 +27,7 @@ let
       configEnv = lib.concatMapAttrs (name: value: lib.optionalAttrs (value != null) {
         ${nameToEnvVar name} = if lib.isBool value then lib.boolToString value else toString value;
       }) cfg.config;
-    in { DATA_FOLDER = "/var/lib/${StateDirectory}"; } // lib.optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
+    in { DATA_FOLDER = dataDir; } // lib.optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
       WEB_VAULT_FOLDER = "${cfg.webVaultPackage}/share/vaultwarden/vault";
     } // configEnv;
 
@@ -160,10 +162,16 @@ in {
   };
 
   config = lib.mkIf cfg.enable {
-    assertions = [ {
-      assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite";
-      message = "Backups for database backends other than sqlite will need customization";
-    } ];
+    assertions = [
+      {
+        assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite";
+        message = "Backups for database backends other than sqlite will need customization";
+      }
+      {
+        assertion = cfg.backupDir != null -> !(lib.hasPrefix dataDir cfg.backupDir);
+        message = "Backup directory can not be in ${dataDir}";
+      }
+    ];
 
     users.users.vaultwarden = {
       inherit group;
@@ -224,7 +232,7 @@ in {
     systemd.services.backup-vaultwarden = lib.mkIf (cfg.backupDir != null) {
       description = "Backup vaultwarden";
       environment = {
-        DATA_FOLDER = "/var/lib/${StateDirectory}";
+        DATA_FOLDER = dataDir;
         BACKUP_FOLDER = cfg.backupDir;
       };
       path = with pkgs; [ sqlite ];
diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix
index 6b05886756fe2..d274b508300f2 100644
--- a/nixos/modules/services/ttys/kmscon.nix
+++ b/nixos/modules/services/ttys/kmscon.nix
@@ -105,7 +105,7 @@ in {
           ));
         render = optionals cfg.hwRender [ "drm" "hwaccel" ];
         fonts = optional (cfg.fonts != null) "font-name=${lib.concatMapStringsSep ", " (f: f.name) cfg.fonts}";
-      in lib.concatStringsSep "\n" (xkb ++ render ++ fonts);
+      in lib.concatLines (xkb ++ render ++ fonts);
 
     hardware.graphics.enable = mkIf cfg.hwRender true;
 
diff --git a/nixos/modules/services/video/frigate.nix b/nixos/modules/services/video/frigate.nix
index c3ec4a3c76c34..5fa67b5ca2649 100644
--- a/nixos/modules/services/video/frigate.nix
+++ b/nixos/modules/services/video/frigate.nix
@@ -403,7 +403,7 @@ in
       path = with pkgs; [
         # unfree:
         # config.boot.kernelPackages.nvidiaPackages.latest.bin
-        ffmpeg_5-headless
+        ffmpeg-headless
         libva-utils
         procps
         radeontop
diff --git a/nixos/modules/services/video/replay-sorcery.nix b/nixos/modules/services/video/replay-sorcery.nix
deleted file mode 100644
index abe7202a4a862..0000000000000
--- a/nixos/modules/services/video/replay-sorcery.nix
+++ /dev/null
@@ -1,72 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.replay-sorcery;
-  configFile = generators.toKeyValue {} cfg.settings;
-in
-{
-  options = with types; {
-    services.replay-sorcery = {
-      enable = mkEnableOption "the ReplaySorcery service for instant-replays";
-
-      enableSysAdminCapability = mkEnableOption ''
-        the system admin capability to support hardware accelerated
-        video capture. This is equivalent to running ReplaySorcery as
-        root, so use with caution'';
-
-      autoStart = mkOption {
-        type = bool;
-        default = false;
-        description = "Automatically start ReplaySorcery when graphical-session.target starts.";
-      };
-
-      settings = mkOption {
-        type = attrsOf (oneOf [ str int ]);
-        default = {};
-        description = "System-wide configuration for ReplaySorcery (/etc/replay-sorcery.conf).";
-        example = literalExpression ''
-          {
-            videoInput = "hwaccel"; # requires `services.replay-sorcery.enableSysAdminCapability = true`
-            videoFramerate = 60;
-          }
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment = {
-      systemPackages = [ pkgs.replay-sorcery ];
-      etc."replay-sorcery.conf".text = configFile;
-    };
-
-    security.wrappers = mkIf cfg.enableSysAdminCapability {
-      replay-sorcery = {
-        owner = "root";
-        group = "root";
-        capabilities = "cap_sys_admin+ep";
-        source = "${pkgs.replay-sorcery}/bin/replay-sorcery";
-      };
-    };
-
-    systemd = {
-      packages = [ pkgs.replay-sorcery ];
-      user.services.replay-sorcery = {
-        wantedBy = mkIf cfg.autoStart [ "graphical-session.target" ];
-        partOf = mkIf cfg.autoStart [ "graphical-session.target" ];
-        serviceConfig = {
-          ExecStart = mkIf cfg.enableSysAdminCapability [
-            "" # Tell systemd to clear the existing ExecStart list, to prevent appending to it.
-            "${config.security.wrapperDir}/replay-sorcery"
-          ];
-        };
-      };
-    };
-  };
-
-  meta = {
-    maintainers = with maintainers; [ kira-bruneau ];
-  };
-}
diff --git a/nixos/modules/services/web-apps/akkoma.nix b/nixos/modules/services/web-apps/akkoma.nix
index 8ba3c7eaa1e6a..17c221778d89a 100644
--- a/nixos/modules/services/web-apps/akkoma.nix
+++ b/nixos/modules/services/web-apps/akkoma.nix
@@ -452,9 +452,9 @@ in {
 
       extraPackages = mkOption {
         type = with types; listOf package;
-        default = with pkgs; [ exiftool ffmpeg_5-headless graphicsmagick-imagemagick-compat ];
-        defaultText = literalExpression "with pkgs; [ exiftool graphicsmagick-imagemagick-compat ffmpeg_5-headless ]";
-        example = literalExpression "with pkgs; [ exiftool imagemagick ffmpeg_5-full ]";
+        default = with pkgs; [ exiftool ffmpeg-headless graphicsmagick-imagemagick-compat ];
+        defaultText = literalExpression "with pkgs; [ exiftool ffmpeg-headless graphicsmagick-imagemagick-compat ]";
+        example = literalExpression "with pkgs; [ exiftool ffmpeg-full imagemagick ]";
         description = ''
           List of extra packages to include in the executable search path of the service unit.
           These are needed by various configurable components such as:
diff --git a/nixos/modules/services/web-apps/cryptpad.nix b/nixos/modules/services/web-apps/cryptpad.nix
new file mode 100644
index 0000000000000..770eefc007395
--- /dev/null
+++ b/nixos/modules/services/web-apps/cryptpad.nix
@@ -0,0 +1,293 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.cryptpad;
+
+  inherit (lib)
+    mkIf
+    mkMerge
+    mkOption
+    strings
+    types
+    ;
+
+  # The Cryptpad configuration file isn't JSON, but a JavaScript source file that assigns a JSON value
+  # to a variable.
+  cryptpadConfigFile = builtins.toFile "cryptpad_config.js" ''
+    module.exports = ${builtins.toJSON cfg.settings}
+  '';
+
+  # Derive domain names for Nginx configuration from Cryptpad configuration
+  mainDomain = strings.removePrefix "https://" cfg.settings.httpUnsafeOrigin;
+  sandboxDomain =
+    if cfg.settings.httpSafeOrigin == null then
+      mainDomain
+    else
+      strings.removePrefix "https://" cfg.settings.httpSafeOrigin;
+
+in
+{
+  options.services.cryptpad = {
+    enable = lib.mkEnableOption "cryptpad";
+
+    package = lib.mkPackageOption pkgs "cryptpad" { };
+
+    configureNginx = mkOption {
+      description = ''
+        Configure Nginx as a reverse proxy for Cryptpad.
+        Note that this makes some assumptions on your setup, and sets settings that will
+        affect other virtualHosts running on your Nginx instance, if any.
+        Alternatively you can configure a reverse-proxy of your choice.
+      '';
+      type = types.bool;
+      default = false;
+    };
+
+    settings = mkOption {
+      description = ''
+        Cryptpad configuration settings.
+        See https://github.com/cryptpad/cryptpad/blob/main/config/config.example.js for a more extensive
+        reference documentation.
+        Test your deployed instance through `https://<domain>/checkup/`.
+      '';
+      type = types.submodule {
+        freeformType = (pkgs.formats.json { }).type;
+        options = {
+          httpUnsafeOrigin = mkOption {
+            type = types.str;
+            example = "https://cryptpad.example.com";
+            default = "";
+            description = "This is the URL that users will enter to load your instance";
+          };
+          httpSafeOrigin = mkOption {
+            type = types.nullOr types.str;
+            example = "https://cryptpad-ui.example.com. Apparently optional but recommended.";
+            description = "Cryptpad sandbox URL";
+          };
+          httpAddress = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            description = "Address on which the Node.js server should listen";
+          };
+          httpPort = mkOption {
+            type = types.int;
+            default = 3000;
+            description = "Port on which the Node.js server should listen";
+          };
+          websocketPort = mkOption {
+            type = types.int;
+            default = 3003;
+            description = "Port for the websocket that needs to be separate";
+          };
+          maxWorkers = mkOption {
+            type = types.nullOr types.int;
+            default = null;
+            description = "Number of child processes, defaults to number of cores available";
+          };
+          adminKeys = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            description = "List of public signing keys of users that can access the admin panel";
+            example = [ "[cryptpad-user1@my.awesome.website/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=]" ];
+          };
+          logToStdout = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Controls whether log output should go to stdout of the systemd service";
+          };
+          logLevel = mkOption {
+            type = types.str;
+            default = "info";
+            description = "Controls log level";
+          };
+          blockDailyCheck = mkOption {
+            type = types.bool;
+            default = true;
+            description = ''
+              Disable telemetry. This setting is only effective if the 'Disable server telemetry'
+              setting in the admin menu has been untouched, and will be ignored by cryptpad once
+              that option is set either way.
+              Note that due to the service confinement, just enabling the option in the admin
+              menu will not be able to resolve DNS and fail; this setting must be set as well.
+            '';
+          };
+          installMethod = mkOption {
+            type = types.str;
+            default = "nixos";
+            description = ''
+              Install method is listed in telemetry if you agree to it through the consentToContact
+              setting in the admin panel.
+            '';
+          };
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      systemd.services.cryptpad = {
+        description = "Cryptpad service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "networking.target" ];
+        serviceConfig = {
+          BindReadOnlyPaths = [
+            cryptpadConfigFile
+            # apparently needs proc for workers management
+            "/proc"
+            "/dev/urandom"
+          ];
+          DynamicUser = true;
+          Environment = [
+            "CRYPTPAD_CONFIG=${cryptpadConfigFile}"
+            "HOME=%S/cryptpad"
+          ];
+          ExecStart = lib.getExe cfg.package;
+          Restart = "always";
+          StateDirectory = "cryptpad";
+          WorkingDirectory = "%S/cryptpad";
+          # security way too many numerous options, from the systemd-analyze security output
+          # at end of test: block everything except
+          # - SystemCallFiters=@resources is required for node
+          # - MemoryDenyWriteExecute for node JIT
+          # - RestrictAddressFamilies=~AF_(INET|INET6) / PrivateNetwork to bind to sockets
+          # - IPAddressDeny likewise allow localhost if binding to localhost or any otherwise
+          # - PrivateUsers somehow service doesn't start with that
+          # - DeviceAllow (char-rtc r added by ProtectClock)
+          AmbientCapabilities = "";
+          CapabilityBoundingSet = "";
+          DeviceAllow = "";
+          LockPersonality = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateTmp = true;
+          ProcSubset = "pid";
+          ProtectClock = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelLogs = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          ProtectProc = "invisible";
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = [
+            "AF_INET"
+            "AF_INET6"
+          ];
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          RuntimeDirectoryMode = "700";
+          SocketBindAllow = [
+            "tcp:${builtins.toString cfg.settings.httpPort}"
+            "tcp:${builtins.toString cfg.settings.websocketPort}"
+          ];
+          SocketBindDeny = [ "any" ];
+          StateDirectoryMode = "0700";
+          SystemCallArchitectures = "native";
+          SystemCallFilter = [
+            "@pkey"
+            "@system-service"
+            "~@chown"
+            "~@keyring"
+            "~@memlock"
+            "~@privileged"
+            "~@resources"
+            "~@setuid"
+            "~@timer"
+          ];
+          UMask = "0077";
+        };
+        confinement = {
+          enable = true;
+          binSh = null;
+          mode = "chroot-only";
+        };
+      };
+    }
+    # block external network access if not phoning home and
+    # binding to localhost (default)
+    (mkIf
+      (
+        cfg.settings.blockDailyCheck
+        && (builtins.elem cfg.settings.httpAddress [
+          "127.0.0.1"
+          "::1"
+        ])
+      )
+      {
+        systemd.services.cryptpad = {
+          serviceConfig = {
+            IPAddressAllow = [ "localhost" ];
+            IPAddressDeny = [ "any" ];
+          };
+        };
+      }
+    )
+    # .. conversely allow DNS & TLS if telemetry is explicitly enabled
+    (mkIf (!cfg.settings.blockDailyCheck) {
+      systemd.services.cryptpad = {
+        serviceConfig = {
+          BindReadOnlyPaths = [
+            "-/etc/resolv.conf"
+            "-/run/systemd"
+            "/etc/hosts"
+            "/etc/ssl/certs/ca-certificates.crt"
+          ];
+        };
+      };
+    })
+
+    (mkIf cfg.configureNginx {
+      assertions = [
+        {
+          assertion = cfg.settings.httpUnsafeOrigin != "";
+          message = "services.cryptpad.settings.httpUnsafeOrigin is required";
+        }
+        {
+          assertion = strings.hasPrefix "https://" cfg.settings.httpUnsafeOrigin;
+          message = "services.cryptpad.settings.httpUnsafeOrigin must start with https://";
+        }
+        {
+          assertion =
+            cfg.settings.httpSafeOrigin == null || strings.hasPrefix "https://" cfg.settings.httpSafeOrigin;
+          message = "services.cryptpad.settings.httpSafeOrigin must start with https:// (or be unset)";
+        }
+      ];
+      services.nginx = {
+        enable = true;
+        recommendedTlsSettings = true;
+        recommendedProxySettings = true;
+        recommendedOptimisation = true;
+        recommendedGzipSettings = true;
+
+        virtualHosts = mkMerge [
+          {
+            "${mainDomain}" = {
+              serverAliases = lib.optionals (cfg.settings.httpSafeOrigin != null) [ sandboxDomain ];
+              enableACME = lib.mkDefault true;
+              forceSSL = true;
+              locations."/" = {
+                proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.httpPort}";
+                extraConfig = ''
+                  client_max_body_size 150m;
+                '';
+              };
+              locations."/cryptpad_websocket" = {
+                proxyPass = "http://${cfg.settings.httpAddress}:${builtins.toString cfg.settings.websocketPort}";
+                proxyWebsockets = true;
+              };
+            };
+          }
+        ];
+      };
+    })
+  ]);
+}
diff --git a/nixos/modules/services/web-apps/eintopf.nix b/nixos/modules/services/web-apps/eintopf.nix
new file mode 100644
index 0000000000000..a2b304445597e
--- /dev/null
+++ b/nixos/modules/services/web-apps/eintopf.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.eintopf;
+
+in {
+  options.services.eintopf = {
+
+    enable = mkEnableOption "Eintopf community event calendar web app";
+
+    settings = mkOption {
+      type = types.attrsOf types.str;
+      default = { };
+      description = ''
+        Settings to configure web service. See
+        <https://codeberg.org/Klasse-Methode/eintopf/src/branch/main/DEPLOYMENT.md>
+        for available options.
+      '';
+      example = literalExpression ''
+        {
+          EINTOPF_ADDR = ":1234";
+          EINTOPF_ADMIN_EMAIL = "admin@example.org";
+          EINTOPF_TIMEZONE = "Europe/Berlin";
+        }
+      '';
+    };
+
+    secrets = lib.mkOption {
+      type = with types; listOf path;
+      description = ''
+        A list of files containing the various secrets. Should be in the
+        format expected by systemd's `EnvironmentFile` directory.
+      '';
+      default = [ ];
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.eintopf = {
+      description = "Community event calendar web app";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      environment = cfg.settings;
+      serviceConfig = {
+        ExecStart = "${pkgs.eintopf}/bin/eintopf";
+        WorkingDirectory = "/var/lib/eintopf";
+        StateDirectory = "eintopf" ;
+        EnvironmentFile = [ cfg.secrets ];
+
+        # hardening
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "" ;
+        DevicePolicy = "closed";
+        DynamicUser = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        ProtectSystem = "strict";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [ "@system-service" "~@privileged" ];
+        UMask = "0077";
+      };
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ onny ];
+
+}
diff --git a/nixos/modules/services/web-apps/freshrss.nix b/nixos/modules/services/web-apps/freshrss.nix
index 7a22e15231923..53366e2c891e9 100644
--- a/nixos/modules/services/web-apps/freshrss.nix
+++ b/nixos/modules/services/web-apps/freshrss.nix
@@ -19,7 +19,7 @@ in
   meta.maintainers = with maintainers; [ etu stunkymonkey mattchrist ];
 
   options.services.freshrss = {
-    enable = mkEnableOption "FreshRSS RSS aggregator and reader with php-fpm backend.";
+    enable = mkEnableOption "FreshRSS RSS aggregator and reader with php-fpm backend";
 
     package = mkPackageOption pkgs "freshrss" { };
 
@@ -281,7 +281,10 @@ in
               ${if cfg.database.passFile != null then "--db-password" else null} = ''"$(cat ${cfg.database.passFile})"'';
               ${if cfg.database.user != null then "--db-user" else null} = ''"${cfg.database.user}"'';
               ${if cfg.database.tableprefix != null then "--db-prefix" else null} = ''"${cfg.database.tableprefix}"'';
+              # hostname:port e.g. "localhost:5432"
               ${if cfg.database.host != null && cfg.database.port != null then "--db-host" else null} = ''"${cfg.database.host}:${toString cfg.database.port}"'';
+              # socket path e.g. "/run/postgresql"
+              ${if cfg.database.host != null && cfg.database.port == null then "--db-host" else null} = ''"${cfg.database.host}"'';
             });
         in
         {
diff --git a/nixos/modules/services/web-apps/glance.md b/nixos/modules/services/web-apps/glance.md
new file mode 100644
index 0000000000000..f65b32b3ba914
--- /dev/null
+++ b/nixos/modules/services/web-apps/glance.md
@@ -0,0 +1,39 @@
+# Glance {#module-services-glance}
+
+Glance is a self-hosted dashboard that puts all your feeds in one place.
+
+Visit [the Glance project page](https://github.com/glanceapp/glance) to learn
+more about it.
+
+## Quickstart {#module-services-glance-quickstart}
+
+Checkout the [configuration docs](https://github.com/glanceapp/glance/blob/main/docs/configuration.md) to learn more.
+Use the following configuration to start a public instance of Glance locally:
+
+```nix
+{
+  services.glance = {
+    enable = true;
+    settings = {
+      pages = [
+        {
+          name = "Home";
+          columns = [
+            {
+              size = "full";
+              widgets = [
+                { type = "calendar"; }
+                {
+                  type = "weather";
+                  location = "Nivelles, Belgium";
+                }
+              ];
+            }
+          ];
+        }
+      ];
+    };
+    openFirewall = true;
+  };
+}
+```
diff --git a/nixos/modules/services/web-apps/glance.nix b/nixos/modules/services/web-apps/glance.nix
new file mode 100644
index 0000000000000..fbc310daea770
--- /dev/null
+++ b/nixos/modules/services/web-apps/glance.nix
@@ -0,0 +1,141 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+let
+  cfg = config.services.glance;
+
+  inherit (lib)
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    mkIf
+    getExe
+    types
+    ;
+
+  settingsFormat = pkgs.formats.yaml { };
+in
+{
+  options.services.glance = {
+    enable = mkEnableOption "glance";
+    package = mkPackageOption pkgs "glance" { };
+
+    settings = mkOption {
+      type = types.submodule {
+        freeformType = settingsFormat.type;
+        options = {
+          server = {
+            host = mkOption {
+              description = "Glance bind address";
+              default = "127.0.0.1";
+              example = "0.0.0.0";
+              type = types.str;
+            };
+            port = mkOption {
+              description = "Glance port to listen on";
+              default = 8080;
+              example = 5678;
+              type = types.port;
+            };
+          };
+          pages = mkOption {
+            type = settingsFormat.type;
+            description = ''
+              List of pages to be present on the dashboard.
+
+              See <https://github.com/glanceapp/glance/blob/main/docs/configuration.md#pages--columns>
+            '';
+            default = [
+              {
+                name = "Calendar";
+                columns = [
+                  {
+                    size = "full";
+                    widgets = [ { type = "calendar"; } ];
+                  }
+                ];
+              }
+            ];
+            example = [
+              {
+                name = "Home";
+                columns = [
+                  {
+                    size = "full";
+                    widgets = [
+                      { type = "calendar"; }
+                      {
+                        type = "weather";
+                        location = "Nivelles, Belgium";
+                      }
+                    ];
+                  }
+                ];
+              }
+            ];
+          };
+        };
+      };
+      default = { };
+      description = ''
+        Configuration written to a yaml file that is read by glance. See
+        <https://github.com/glanceapp/glance/blob/main/docs/configuration.md>
+        for more.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to open the firewall for Glance.
+        This adds `services.glance.settings.server.port` to `networking.firewall.allowedTCPPorts`.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.glance = {
+      description = "Glance feed dashboard server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart =
+          let
+            glance-yaml = settingsFormat.generate "glance.yaml" cfg.settings;
+          in
+          "${getExe cfg.package} --config ${glance-yaml}";
+        WorkingDirectory = "/var/lib/glance";
+        StateDirectory = "glance";
+        RuntimeDirectory = "glance";
+        RuntimeDirectoryMode = "0755";
+        PrivateTmp = true;
+        DynamicUser = true;
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        PrivateUsers = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProcSubset = "pid";
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        UMask = "0077";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall { allowedTCPPorts = [ cfg.settings.server.port ]; };
+  };
+
+  meta.doc = ./glance.md;
+  meta.maintainers = [ lib.maintainers.drupol ];
+}
diff --git a/nixos/modules/services/web-apps/goatcounter.nix b/nixos/modules/services/web-apps/goatcounter.nix
new file mode 100644
index 0000000000000..318915fb88fff
--- /dev/null
+++ b/nixos/modules/services/web-apps/goatcounter.nix
@@ -0,0 +1,80 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  inherit (lib) types;
+  cfg = config.services.goatcounter;
+  stateDir = "goatcounter";
+in
+
+{
+  options = {
+    services.goatcounter = {
+      enable = lib.mkEnableOption "goatcounter";
+
+      package = lib.mkPackageOption pkgs "goatcounter" { };
+
+      address = lib.mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "Web interface address.";
+      };
+
+      port = lib.mkOption {
+        type = types.port;
+        default = 8081;
+        description = "Web interface port.";
+      };
+
+      proxy = lib.mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether Goatcounter service is running behind a reverse proxy. Will listen for HTTPS if `false`.
+          Refer to [documentation](https://github.com/arp242/goatcounter?tab=readme-ov-file#running) for more details.
+        '';
+      };
+
+      extraArgs = lib.mkOption {
+        type = with types; listOf str;
+        default = [ ];
+        description = ''
+          List of extra arguments to be passed to goatcounter cli.
+          See {command}`goatcounter help serve` for more information.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.goatcounter = {
+      description = "Easy web analytics. No tracking of personal data.";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = lib.escapeShellArgs (
+          [
+            (lib.getExe cfg.package)
+            "serve"
+            "-listen"
+            "${cfg.address}:${toString cfg.port}"
+          ]
+          ++ lib.optionals cfg.proxy [
+            "-tls"
+            "proxy"
+          ]
+          ++ cfg.extraArgs
+        );
+        DynamicUser = true;
+        StateDirectory = stateDir;
+        WorkingDirectory = "%S/${stateDir}";
+        Restart = "always";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ bhankas ];
+}
diff --git a/nixos/modules/services/web-apps/gotify-server.nix b/nixos/modules/services/web-apps/gotify-server.nix
index b700fd14ee52f..5f5a9f9f86bbc 100644
--- a/nixos/modules/services/web-apps/gotify-server.nix
+++ b/nixos/modules/services/web-apps/gotify-server.nix
@@ -1,49 +1,90 @@
-{ pkgs, lib, config, ... }:
-
-with lib;
+{
+  pkgs,
+  lib,
+  config,
+  ...
+}:
 
 let
   cfg = config.services.gotify;
-in {
-  options = {
-    services.gotify = {
-      enable = mkEnableOption "Gotify webserver";
-
-      port = mkOption {
-        type = types.port;
-        description = ''
-          Port the server listens to.
-        '';
-      };
+in
+{
+  imports = [
+    (lib.mkRenamedOptionModule
+      [
+        "services"
+        "gotify"
+        "port"
+      ]
+      [
+        "services"
+        "gotify"
+        "environment"
+        "GOTIFY_SERVER_PORT"
+      ]
+    )
+  ];
+
+  options.services.gotify = {
+    enable = lib.mkEnableOption "Gotify webserver";
 
-      stateDirectoryName = mkOption {
-        type = types.str;
-        default = "gotify-server";
-        description = ''
-          The name of the directory below {file}`/var/lib` where
-          gotify stores its runtime data.
-        '';
+    package = lib.mkPackageOption pkgs "gotify-server" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf (
+        lib.types.oneOf [
+          lib.types.str
+          lib.types.int
+        ]
+      );
+      default = { };
+      example = {
+        GOTIFY_SERVER_PORT = 8080;
+        GOTIFY_DATABASE_DIALECT = "sqlite3";
       };
+      description = ''
+        Config environment variables for the gotify-server.
+        See https://gotify.net/docs/config for more details.
+      '';
+    };
+
+    environmentFiles = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      description = ''
+        Files containing additional config environment variables for gotify-server.
+        Secrets should be set in environmentFiles instead of environment.
+      '';
+    };
+
+    stateDirectoryName = lib.mkOption {
+      type = lib.types.str;
+      default = "gotify-server";
+      description = ''
+        The name of the directory below {file}`/var/lib` where
+        gotify stores its runtime data.
+      '';
     };
   };
 
-  config = mkIf cfg.enable {
+  config = lib.mkIf cfg.enable {
     systemd.services.gotify-server = {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
       description = "Simple server for sending and receiving messages";
 
-      environment = {
-        GOTIFY_SERVER_PORT = toString cfg.port;
-      };
+      environment = lib.mapAttrs (_: toString) cfg.environment;
 
       serviceConfig = {
         WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
         StateDirectory = cfg.stateDirectoryName;
+        EnvironmentFile = cfg.environmentFiles;
         Restart = "always";
-        DynamicUser = "yes";
-        ExecStart = "${pkgs.gotify-server}/bin/server";
+        DynamicUser = true;
+        ExecStart = lib.getExe cfg.package;
       };
     };
   };
+
+  meta.maintainers = with lib.maintainers; [ DCsunset ];
 }
diff --git a/nixos/modules/services/web-apps/ifm.nix b/nixos/modules/services/web-apps/ifm.nix
new file mode 100644
index 0000000000000..d5621866a9a3c
--- /dev/null
+++ b/nixos/modules/services/web-apps/ifm.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ...}:
+let
+  cfg = config.services.ifm;
+
+  version = "4.0.2";
+  src = pkgs.fetchurl {
+    url = "https://github.com/misterunknown/ifm/releases/download/v${version}/cdn.ifm.php";
+    hash = "sha256-37WbRM6D7JGmd//06zMhxMGIh8ioY8vRUmxX4OHgqBE=";
+  };
+
+  php = pkgs.php83;
+in {
+  options.services.ifm = {
+    enable = lib.mkEnableOption ''
+      Improved file manager, a single-file web-based filemanager
+
+      Lightweight and minimal, served using PHP's built-in server
+  '';
+
+    dataDir = lib.mkOption {
+      type = lib.types.str;
+      description = "Directory to serve throught the file managing service";
+    };
+
+    listenAddress = lib.mkOption {
+      type = lib.types.str;
+      default = "127.0.0.1";
+      description = "Address on which the service is listening";
+      example = "0.0.0.0";
+    };
+
+    port = lib.mkOption {
+      type = lib.types.port;
+      default = 9090;
+      description = "Port on which to serve the IFM service";
+    };
+
+    settings = lib.mkOption {
+      type = with lib.types; attrsOf anything;
+      default = {};
+      description = ''
+        Configuration of the IFM service.
+
+        See [the documentation](https://github.com/misterunknown/ifm/wiki/Configuration)
+        for available options and default values.
+      '';
+      example = {
+        IFM_GUI_SHOWPATH = 0;
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.ifm = {
+      description = "Improved file manager, a single-file web based filemanager";
+
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        IFM_ROOT_DIR = "/data";
+      } // (builtins.mapAttrs (_: val: toString val) cfg.settings);
+
+      script = ''
+        mkdir -p /tmp/ifm
+        ln -s ${src} /tmp/ifm/index.php
+        ${lib.getExe php} -S ${cfg.listenAddress}:${builtins.toString cfg.port} -t /tmp/ifm
+      '';
+
+      serviceConfig = {
+        DynamicUser = true;
+        User = "ifm";
+        StandardOutput = "journal";
+        BindPaths = "${cfg.dataDir}:/data";
+        PrivateTmp = true;
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ litchipi ];
+}
diff --git a/nixos/modules/services/web-apps/jitsi-meet.nix b/nixos/modules/services/web-apps/jitsi-meet.nix
index 247b65c786636..39aa7379c0edf 100644
--- a/nixos/modules/services/web-apps/jitsi-meet.nix
+++ b/nixos/modules/services/web-apps/jitsi-meet.nix
@@ -398,30 +398,29 @@ in
       before = [ "jicofo.service" "jitsi-videobridge2.service" ] ++ (optional cfg.prosody.enable "prosody.service") ++ (optional cfg.jigasi.enable "jigasi.service");
       serviceConfig = {
         Type = "oneshot";
+        UMask = "027";
+        User = "root";
+        Group = "jitsi-meet";
+        WorkingDirectory = "/var/lib/jitsi-meet";
       };
 
       script = let
         secrets = [ "jicofo-component-secret" "jicofo-user-secret" "jibri-auth-secret" "jibri-recorder-secret" ] ++ (optionals cfg.jigasi.enable [ "jigasi-user-secret" "jigasi-component-secret" ]) ++ (optional (cfg.videobridge.passwordFile == null) "videobridge-secret");
       in
       ''
-        cd /var/lib/jitsi-meet
         ${concatMapStringsSep "\n" (s: ''
           if [ ! -f ${s} ]; then
             tr -dc a-zA-Z0-9 </dev/urandom | head -c 64 > ${s}
-            chown root:jitsi-meet ${s}
-            chmod 640 ${s}
           fi
         '') secrets}
 
         # for easy access in prosody
         echo "JICOFO_COMPONENT_SECRET=$(cat jicofo-component-secret)" > secrets-env
         echo "JIGASI_COMPONENT_SECRET=$(cat jigasi-component-secret)" >> secrets-env
-        chown root:jitsi-meet secrets-env
-        chmod 640 secrets-env
       ''
       + optionalString cfg.prosody.enable ''
         # generate self-signed certificates
-        if [ ! -f /var/lib/jitsi-meet.crt ]; then
+        if [ ! -f /var/lib/jitsi-meet/jitsi-meet.crt ]; then
           ${getBin pkgs.openssl}/bin/openssl req \
             -x509 \
             -newkey rsa:4096 \
@@ -430,8 +429,7 @@ in
             -days 36500 \
             -nodes \
             -subj '/CN=${cfg.hostName}/CN=auth.${cfg.hostName}'
-          chmod 640 /var/lib/jitsi-meet/jitsi-meet.{crt,key}
-          chown root:jitsi-meet /var/lib/jitsi-meet/jitsi-meet.{crt,key}
+          chmod 640 /var/lib/jitsi-meet/jitsi-meet.key
         fi
       '';
     };
diff --git a/nixos/modules/services/web-apps/mealie.nix b/nixos/modules/services/web-apps/mealie.nix
index 2484b2489c0d0..ab94103954e13 100644
--- a/nixos/modules/services/web-apps/mealie.nix
+++ b/nixos/modules/services/web-apps/mealie.nix
@@ -57,7 +57,6 @@ in
 
       environment = {
         PRODUCTION = "true";
-        ALEMBIC_CONFIG_FILE="${pkg}/config/alembic.ini";
         API_PORT = toString cfg.port;
         BASE_URL = "http://localhost:${toString cfg.port}";
         DATA_DIR = "/var/lib/mealie";
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
index b11626ec2dc3b..22e61ea3a6836 100644
--- a/nixos/modules/services/web-apps/mediawiki.nix
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -19,7 +19,7 @@ let
   stateDir = "/var/lib/mediawiki";
 
   # https://www.mediawiki.org/wiki/Compatibility
-  php = pkgs.php81;
+  php = pkgs.php82;
 
   pkg = pkgs.stdenv.mkDerivation rec {
     pname = "mediawiki-full";
@@ -64,129 +64,135 @@ let
   else
     throw "Unsupported database type: ${cfg.database.type} for socket: ${cfg.database.socket}";
 
-  mediawikiConfig = pkgs.writeText "LocalSettings.php" ''
-    <?php
-      # Protect against web entry
-      if ( !defined( 'MEDIAWIKI' ) ) {
-        exit;
-      }
+  mediawikiConfig = pkgs.writeTextFile {
+    name = "LocalSettings.php";
+    checkPhase = ''
+      ${php}/bin/php --syntax-check "$target"
+    '';
+    text = ''
+      <?php
+        # Protect against web entry
+        if ( !defined( 'MEDIAWIKI' ) ) {
+          exit;
+        }
 
-      $wgSitename = "${cfg.name}";
-      $wgMetaNamespace = false;
-
-      ## The URL base path to the directory containing the wiki;
-      ## defaults for all runtime URL paths are based off of this.
-      ## For more information on customizing the URLs
-      ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
-      ## https://www.mediawiki.org/wiki/Manual:Short_URL
-      $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}";
+        $wgSitename = "${cfg.name}";
+        $wgMetaNamespace = false;
+
+        ## The URL base path to the directory containing the wiki;
+        ## defaults for all runtime URL paths are based off of this.
+        ## For more information on customizing the URLs
+        ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
+        ## https://www.mediawiki.org/wiki/Manual:Short_URL
+        $wgScriptPath = "${lib.optionalString (cfg.webserver == "nginx") "/w"}";
 
-      ## The protocol and server name to use in fully-qualified URLs
-      $wgServer = "${cfg.url}";
+        ## The protocol and server name to use in fully-qualified URLs
+        $wgServer = "${cfg.url}";
 
-      ## The URL path to static resources (images, scripts, etc.)
-      $wgResourceBasePath = $wgScriptPath;
+        ## The URL path to static resources (images, scripts, etc.)
+        $wgResourceBasePath = $wgScriptPath;
 
-      ${lib.optionalString (cfg.webserver == "nginx") ''
-        $wgArticlePath = "/wiki/$1";
-        $wgUsePathInfo = true;
-      ''}
+        ${lib.optionalString (cfg.webserver == "nginx") ''
+          $wgArticlePath = "/wiki/$1";
+          $wgUsePathInfo = true;
+        ''}
 
-      ## The URL path to the logo.  Make sure you change this from the default,
-      ## or else you'll overwrite your logo when you upgrade!
-      $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
+        ## The URL path to the logo.  Make sure you change this from the default,
+        ## or else you'll overwrite your logo when you upgrade!
+        $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
 
-      ## UPO means: this is also a user preference option
+        ## UPO means: this is also a user preference option
 
-      $wgEnableEmail = true;
-      $wgEnableUserEmail = true; # UPO
+        $wgEnableEmail = true;
+        $wgEnableUserEmail = true; # UPO
 
-      $wgPasswordSender = "${cfg.passwordSender}";
+        $wgPasswordSender = "${cfg.passwordSender}";
 
-      $wgEnotifUserTalk = false; # UPO
-      $wgEnotifWatchlist = false; # UPO
-      $wgEmailAuthentication = true;
+        $wgEnotifUserTalk = false; # UPO
+        $wgEnotifWatchlist = false; # UPO
+        $wgEmailAuthentication = true;
 
-      ## Database settings
-      $wgDBtype = "${cfg.database.type}";
-      $wgDBserver = "${dbAddr}";
-      $wgDBport = "${toString cfg.database.port}";
-      $wgDBname = "${cfg.database.name}";
-      $wgDBuser = "${cfg.database.user}";
-      ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
+        ## Database settings
+        $wgDBtype = "${cfg.database.type}";
+        $wgDBserver = "${dbAddr}";
+        $wgDBport = "${toString cfg.database.port}";
+        $wgDBname = "${cfg.database.name}";
+        $wgDBuser = "${cfg.database.user}";
+        ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
 
-      ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) ''
-        # MySQL specific settings
-        $wgDBprefix = "${cfg.database.tablePrefix}";
-      ''}
+        ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) ''
+          # MySQL specific settings
+          $wgDBprefix = "${cfg.database.tablePrefix}";
+        ''}
 
-      ${optionalString (cfg.database.type == "mysql") ''
-        # MySQL table options to use during installation or update
-        $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
-      ''}
+        ${optionalString (cfg.database.type == "mysql") ''
+          # MySQL table options to use during installation or update
+          $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
+        ''}
 
-      ## Shared memory settings
-      $wgMainCacheType = CACHE_NONE;
-      $wgMemCachedServers = [];
+        ## Shared memory settings
+        $wgMainCacheType = CACHE_NONE;
+        $wgMemCachedServers = [];
 
-      ${optionalString (cfg.uploadsDir != null) ''
-        $wgEnableUploads = true;
-        $wgUploadDirectory = "${cfg.uploadsDir}";
-      ''}
+        ${optionalString (cfg.uploadsDir != null) ''
+          $wgEnableUploads = true;
+          $wgUploadDirectory = "${cfg.uploadsDir}";
+        ''}
 
-      $wgUseImageMagick = true;
-      $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
+        $wgUseImageMagick = true;
+        $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
 
-      # InstantCommons allows wiki to use images from https://commons.wikimedia.org
-      $wgUseInstantCommons = false;
+        # InstantCommons allows wiki to use images from https://commons.wikimedia.org
+        $wgUseInstantCommons = false;
 
-      # Periodically send a pingback to https://www.mediawiki.org/ with basic data
-      # about this MediaWiki instance. The Wikimedia Foundation shares this data
-      # with MediaWiki developers to help guide future development efforts.
-      $wgPingback = true;
+        # Periodically send a pingback to https://www.mediawiki.org/ with basic data
+        # about this MediaWiki instance. The Wikimedia Foundation shares this data
+        # with MediaWiki developers to help guide future development efforts.
+        $wgPingback = true;
 
-      ## If you use ImageMagick (or any other shell command) on a
-      ## Linux server, this will need to be set to the name of an
-      ## available UTF-8 locale
-      $wgShellLocale = "C.UTF-8";
+        ## If you use ImageMagick (or any other shell command) on a
+        ## Linux server, this will need to be set to the name of an
+        ## available UTF-8 locale
+        $wgShellLocale = "C.UTF-8";
 
-      ## Set $wgCacheDirectory to a writable directory on the web server
-      ## to make your wiki go slightly faster. The directory should not
-      ## be publicly accessible from the web.
-      $wgCacheDirectory = "${cacheDir}";
+        ## Set $wgCacheDirectory to a writable directory on the web server
+        ## to make your wiki go slightly faster. The directory should not
+        ## be publicly accessible from the web.
+        $wgCacheDirectory = "${cacheDir}";
 
-      # Site language code, should be one of the list in ./languages/data/Names.php
-      $wgLanguageCode = "en";
+        # Site language code, should be one of the list in ./languages/data/Names.php
+        $wgLanguageCode = "en";
 
-      $wgSecretKey = file_get_contents("${stateDir}/secret.key");
+        $wgSecretKey = file_get_contents("${stateDir}/secret.key");
 
-      # Changing this will log out all existing sessions.
-      $wgAuthenticationTokenVersion = "";
+        # Changing this will log out all existing sessions.
+        $wgAuthenticationTokenVersion = "";
 
-      ## For attaching licensing metadata to pages, and displaying an
-      ## appropriate copyright notice / icon. GNU Free Documentation
-      ## License and Creative Commons licenses are supported so far.
-      $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
-      $wgRightsUrl = "";
-      $wgRightsText = "";
-      $wgRightsIcon = "";
+        ## For attaching licensing metadata to pages, and displaying an
+        ## appropriate copyright notice / icon. GNU Free Documentation
+        ## License and Creative Commons licenses are supported so far.
+        $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
+        $wgRightsUrl = "";
+        $wgRightsText = "";
+        $wgRightsIcon = "";
 
-      # Path to the GNU diff3 utility. Used for conflict resolution.
-      $wgDiff = "${pkgs.diffutils}/bin/diff";
-      $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
+        # Path to the GNU diff3 utility. Used for conflict resolution.
+        $wgDiff = "${pkgs.diffutils}/bin/diff";
+        $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
 
-      # Enabled skins.
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)}
+        # Enabled skins.
+        ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)}
 
-      # Enabled extensions.
-      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)}
+        # Enabled extensions.
+        ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)}
 
 
-      # End of automatically generated settings.
-      # Add more configuration options below.
+        # End of automatically generated settings.
+        # Add more configuration options below.
 
-      ${cfg.extraConfig}
-  '';
+        ${cfg.extraConfig}
+      '';
+    };
 
   withTrailingSlash = str: if lib.hasSuffix "/" str then str else "${str}/";
 in
diff --git a/nixos/modules/services/web-apps/meme-bingo-web.nix b/nixos/modules/services/web-apps/meme-bingo-web.nix
index ab123ba8ef9ca..4623bc22c26ff 100644
--- a/nixos/modules/services/web-apps/meme-bingo-web.nix
+++ b/nixos/modules/services/web-apps/meme-bingo-web.nix
@@ -17,7 +17,7 @@ in {
 
       baseUrl = mkOption {
         description = ''
-          URL to be used for the HTML <base> element on all HTML routes.
+          URL to be used for the HTML \<base\> element on all HTML routes.
         '';
         type = types.str;
         default = "http://localhost:41678/";
@@ -36,7 +36,7 @@ in {
 
   config = mkIf cfg.enable {
     systemd.services.meme-bingo-web = {
-      description = "A web app for playing meme bingos.";
+      description = "A web app for playing meme bingos";
       wantedBy = [ "multi-user.target" ];
 
       environment = {
@@ -59,6 +59,7 @@ in {
         # Hardening
         CapabilityBoundingSet = [ "" ];
         DeviceAllow = [ "/dev/random" ];
+        InaccessiblePaths = [ "/dev/shm" "/sys" ];
         LockPersonality = true;
         PrivateDevices = true;
         PrivateUsers = true;
@@ -73,6 +74,7 @@ in {
         ProtectKernelTunables = true;
         ProtectProc = "invisible";
         RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictFilesystems = [ "@basic-api" "~sysfs" ];
         RestrictNamespaces = true;
         RestrictRealtime = true;
         SystemCallArchitectures = "native";
diff --git a/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 61243a63c582e..b733ceec74dbe 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -49,7 +49,8 @@ in
       };
 
       adminCredentialsFile = mkOption {
-        type = types.path;
+        type = types.nullOr types.path;
+        default = null;
         description = ''
           File containing the ADMIN_USERNAME and
           ADMIN_PASSWORD (length >= 6) in the format of
@@ -61,11 +62,16 @@ in
   };
 
   config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.config.CREATE_ADMIN == 0 || cfg.adminCredentialsFile != null;
+        message = "services.miniflux.adminCredentialsFile must be set if services.miniflux.config.CREATE_ADMIN is 1";
+      }
+    ];
     services.miniflux.config = {
       LISTEN_ADDR = mkDefault defaultAddress;
       DATABASE_URL = lib.mkIf cfg.createDatabaseLocally "user=miniflux host=/run/postgresql dbname=miniflux";
       RUN_MIGRATIONS = 1;
-      CREATE_ADMIN = 1;
+      CREATE_ADMIN = lib.mkDefault 1;
       WATCHDOG = 1;
     };
 
@@ -103,7 +109,7 @@ in
         DynamicUser = true;
         RuntimeDirectory = "miniflux";
         RuntimeDirectoryMode = "0750";
-        EnvironmentFile = cfg.adminCredentialsFile;
+        EnvironmentFile = lib.mkIf (cfg.adminCredentialsFile != null) cfg.adminCredentialsFile;
         WatchdogSec = 60;
         WatchdogSignal = "SIGKILL";
         Restart = "always";
diff --git a/nixos/modules/services/web-apps/misskey.nix b/nixos/modules/services/web-apps/misskey.nix
new file mode 100644
index 0000000000000..8a5c4bd927660
--- /dev/null
+++ b/nixos/modules/services/web-apps/misskey.nix
@@ -0,0 +1,418 @@
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
+
+let
+  cfg = config.services.misskey;
+  settingsFormat = pkgs.formats.yaml { };
+  redisType = lib.types.submodule {
+    freeformType = lib.types.attrsOf settingsFormat.type;
+    options = {
+      host = lib.mkOption {
+        type = lib.types.str;
+        default = "localhost";
+        description = "The Redis host.";
+      };
+      port = lib.mkOption {
+        type = lib.types.port;
+        default = 6379;
+        description = "The Redis port.";
+      };
+    };
+  };
+  settings = lib.mkOption {
+    description = ''
+      Configuration for Misskey, see
+      [`example.yml`](https://github.com/misskey-dev/misskey/blob/develop/.config/example.yml)
+      for all supported options.
+    '';
+    type = lib.types.submodule {
+      freeformType = lib.types.attrsOf settingsFormat.type;
+      options = {
+        url = lib.mkOption {
+          type = lib.types.str;
+          example = "https://example.tld/";
+          description = ''
+            The final user-facing URL. Do not change after running Misskey for the first time.
+
+            This needs to match up with the configured reverse proxy and is automatically configured when using `services.misskey.reverseProxy`.
+          '';
+        };
+        port = lib.mkOption {
+          type = lib.types.port;
+          default = 3000;
+          description = "The port your Misskey server should listen on.";
+        };
+        socket = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          example = "/path/to/misskey.sock";
+          description = "The UNIX socket your Misskey server should listen on.";
+        };
+        chmodSocket = lib.mkOption {
+          type = lib.types.nullOr lib.types.str;
+          default = null;
+          example = "777";
+          description = "The file access mode of the UNIX socket.";
+        };
+        db = lib.mkOption {
+          description = "Database settings.";
+          type = lib.types.submodule {
+            options = {
+              host = lib.mkOption {
+                type = lib.types.str;
+                default = "/var/run/postgresql";
+                example = "localhost";
+                description = "The PostgreSQL host.";
+              };
+              port = lib.mkOption {
+                type = lib.types.port;
+                default = 5432;
+                description = "The PostgreSQL port.";
+              };
+              db = lib.mkOption {
+                type = lib.types.str;
+                default = "misskey";
+                description = "The database name.";
+              };
+              user = lib.mkOption {
+                type = lib.types.str;
+                default = "misskey";
+                description = "The user used for database authentication.";
+              };
+              pass = lib.mkOption {
+                type = lib.types.nullOr lib.types.str;
+                default = null;
+                description = "The password used for database authentication.";
+              };
+              disableCache = lib.mkOption {
+                type = lib.types.bool;
+                default = false;
+                description = "Whether to disable caching queries.";
+              };
+              extra = lib.mkOption {
+                type = lib.types.nullOr (lib.types.attrsOf settingsFormat.type);
+                default = null;
+                example = {
+                  ssl = true;
+                };
+                description = "Extra connection options.";
+              };
+            };
+          };
+          default = { };
+        };
+        redis = lib.mkOption {
+          type = redisType;
+          default = { };
+          description = "`ioredis` options. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
+        };
+        redisForPubsub = lib.mkOption {
+          type = lib.types.nullOr redisType;
+          default = null;
+          description = "`ioredis` options for pubsub. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
+        };
+        redisForJobQueue = lib.mkOption {
+          type = lib.types.nullOr redisType;
+          default = null;
+          description = "`ioredis` options for the job queue. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
+        };
+        redisForTimelines = lib.mkOption {
+          type = lib.types.nullOr redisType;
+          default = null;
+          description = "`ioredis` options for timelines. See [`README`](https://github.com/redis/ioredis?tab=readme-ov-file#connect-to-redis) for reference.";
+        };
+        meilisearch = lib.mkOption {
+          description = "Meilisearch connection options.";
+          type = lib.types.nullOr (
+            lib.types.submodule {
+              options = {
+                host = lib.mkOption {
+                  type = lib.types.str;
+                  default = "localhost";
+                  description = "The Meilisearch host.";
+                };
+                port = lib.mkOption {
+                  type = lib.types.port;
+                  default = 7700;
+                  description = "The Meilisearch port.";
+                };
+                apiKey = lib.mkOption {
+                  type = lib.types.nullOr lib.types.str;
+                  default = null;
+                  description = "The Meilisearch API key.";
+                };
+                ssl = lib.mkOption {
+                  type = lib.types.bool;
+                  default = false;
+                  description = "Whether to connect via SSL.";
+                };
+                index = lib.mkOption {
+                  type = lib.types.nullOr lib.types.str;
+                  default = null;
+                  description = "Meilisearch index to use.";
+                };
+                scope = lib.mkOption {
+                  type = lib.types.enum [
+                    "local"
+                    "global"
+                  ];
+                  default = "local";
+                  description = "The search scope.";
+                };
+              };
+            }
+          );
+          default = null;
+        };
+        id = lib.mkOption {
+          type = lib.types.enum [
+            "aid"
+            "aidx"
+            "meid"
+            "ulid"
+            "objectid"
+          ];
+          default = "aidx";
+          description = "The ID generation method to use. Do not change after starting Misskey for the first time.";
+        };
+      };
+    };
+  };
+in
+
+{
+  options = {
+    services.misskey = {
+      enable = lib.mkEnableOption "misskey";
+      package = lib.mkPackageOption pkgs "misskey" { };
+      inherit settings;
+      database = {
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = "Create the PostgreSQL database locally. Sets `services.misskey.settings.db.{db,host,port,user,pass}`.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          description = "The path to a file containing the database password. Sets `services.misskey.settings.db.pass`.";
+        };
+      };
+      redis = {
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = "Create and use a local Redis instance. Sets `services.misskey.settings.redis.host`.";
+        };
+        passwordFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          description = "The path to a file containing the Redis password. Sets `services.misskey.settings.redis.pass`.";
+        };
+      };
+      meilisearch = {
+        createLocally = lib.mkOption {
+          type = lib.types.bool;
+          default = false;
+          description = "Create and use a local Meilisearch instance. Sets `services.misskey.settings.meilisearch.{host,port,ssl}`.";
+        };
+        keyFile = lib.mkOption {
+          type = lib.types.nullOr lib.types.path;
+          default = null;
+          description = "The path to a file containing the Meilisearch API key. Sets `services.misskey.settings.meilisearch.apiKey`.";
+        };
+      };
+      reverseProxy = {
+        enable = lib.mkEnableOption "a HTTP reverse proxy for Misskey";
+        webserver = lib.mkOption {
+          type = lib.types.attrTag {
+            nginx = lib.mkOption {
+              type = lib.types.submodule (import ../web-servers/nginx/vhost-options.nix);
+              default = { };
+              description = ''
+                Extra configuration for the nginx virtual host of Misskey.
+                Set to `{ }` to use the default configuration.
+              '';
+            };
+            caddy = lib.mkOption {
+              type = lib.types.submodule (
+                import ../web-servers/caddy/vhost-options.nix { cfg = config.services.caddy; }
+              );
+              default = { };
+              description = ''
+                Extra configuration for the caddy virtual host of Misskey.
+                Set to `{ }` to use the default configuration.
+              '';
+            };
+          };
+          description = "The webserver to use as the reverse proxy.";
+        };
+        host = lib.mkOption {
+          type = lib.types.nullOr lib.types.str;
+          description = ''
+            The fully qualified domain name to bind to. Sets `services.misskey.settings.url`.
+
+            This is required when using `services.misskey.reverseProxy.enable = true`.
+          '';
+          example = "misskey.example.com";
+          default = null;
+        };
+        ssl = lib.mkOption {
+          type = lib.types.nullOr lib.types.bool;
+          description = ''
+            Whether to enable SSL for the reverse proxy. Sets `services.misskey.settings.url`.
+
+            This is required when using `services.misskey.reverseProxy.enable = true`.
+          '';
+          example = true;
+          default = null;
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      {
+        assertion =
+          cfg.reverseProxy.enable -> ((cfg.reverseProxy.host != null) && (cfg.reverseProxy.ssl != null));
+        message = "`services.misskey.reverseProxy.enable` requires `services.misskey.reverseProxy.host` and `services.misskey.reverseProxy.ssl` to be set.";
+      }
+    ];
+
+    services.misskey.settings = lib.mkMerge [
+      (lib.mkIf cfg.database.createLocally {
+        db = {
+          db = lib.mkDefault "misskey";
+          # Use unix socket instead of localhost to allow PostgreSQL peer authentication,
+          # required for `services.postgresql.ensureUsers`
+          host = lib.mkDefault "/var/run/postgresql";
+          port = lib.mkDefault config.services.postgresql.settings.port;
+          user = lib.mkDefault "misskey";
+          pass = lib.mkDefault null;
+        };
+      })
+      (lib.mkIf (cfg.database.passwordFile != null) { db.pass = lib.mkDefault "@DATABASE_PASSWORD@"; })
+      (lib.mkIf cfg.redis.createLocally { redis.host = lib.mkDefault "localhost"; })
+      (lib.mkIf (cfg.redis.passwordFile != null) { redis.pass = lib.mkDefault "@REDIS_PASSWORD@"; })
+      (lib.mkIf cfg.meilisearch.createLocally {
+        meilisearch = {
+          host = lib.mkDefault "localhost";
+          port = lib.mkDefault config.services.meilisearch.listenPort;
+          ssl = lib.mkDefault false;
+        };
+      })
+      (lib.mkIf (cfg.meilisearch.keyFile != null) {
+        meilisearch.apiKey = lib.mkDefault "@MEILISEARCH_KEY@";
+      })
+      (lib.mkIf cfg.reverseProxy.enable {
+        url = lib.mkDefault "${
+          if cfg.reverseProxy.ssl then "https" else "http"
+        }://${cfg.reverseProxy.host}";
+      })
+    ];
+
+    systemd.services.misskey = {
+      after = [
+        "network-online.target"
+        "postgresql.service"
+      ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        MISSKEY_CONFIG_YML = "/run/misskey/default.yml";
+      };
+      preStart =
+        ''
+          install -m 700 ${settingsFormat.generate "misskey-config.yml" cfg.settings} /run/misskey/default.yml
+        ''
+        + (lib.optionalString (cfg.database.passwordFile != null) ''
+          ${pkgs.replace-secret}/bin/replace-secret '@DATABASE_PASSWORD@' "${cfg.database.passwordFile}" /run/misskey/default.yml
+        '')
+        + (lib.optionalString (cfg.redis.passwordFile != null) ''
+          ${pkgs.replace-secret}/bin/replace-secret '@REDIS_PASSWORD@' "${cfg.redis.passwordFile}" /run/misskey/default.yml
+        '')
+        + (lib.optionalString (cfg.meilisearch.keyFile != null) ''
+          ${pkgs.replace-secret}/bin/replace-secret '@MEILISEARCH_KEY@' "${cfg.meilisearch.keyFile}" /run/misskey/default.yml
+        '');
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/misskey migrateandstart";
+        RuntimeDirectory = "misskey";
+        RuntimeDirectoryMode = "700";
+        StateDirectory = "misskey";
+        StateDirectoryMode = "700";
+        TimeoutSec = 60;
+        DynamicUser = true;
+        User = "misskey";
+        LockPersonality = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectProc = "invisible";
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
+      };
+    };
+
+    services.postgresql = lib.mkIf cfg.database.createLocally {
+      enable = true;
+      ensureDatabases = [ "misskey" ];
+      ensureUsers = [
+        {
+          name = "misskey";
+          ensureDBOwnership = true;
+        }
+      ];
+    };
+
+    services.redis.servers = lib.mkIf cfg.redis.createLocally {
+      misskey = {
+        enable = true;
+        port = cfg.settings.redis.port;
+      };
+    };
+
+    services.meilisearch = lib.mkIf cfg.meilisearch.createLocally { enable = true; };
+
+    services.caddy = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? caddy) {
+      enable = true;
+      virtualHosts.${cfg.settings.url} = lib.mkMerge [
+        cfg.reverseProxy.webserver.caddy
+        {
+          hostName = lib.mkDefault cfg.settings.url;
+          extraConfig = ''
+            reverse_proxy localhost:${toString cfg.settings.port}
+          '';
+        }
+      ];
+    };
+
+    services.nginx = lib.mkIf (cfg.reverseProxy.enable && cfg.reverseProxy.webserver ? nginx) {
+      enable = true;
+      virtualHosts.${cfg.reverseProxy.host} = lib.mkMerge [
+        cfg.reverseProxy.webserver.nginx
+        {
+          locations."/" = {
+            proxyPass = lib.mkDefault "http://localhost:${toString cfg.settings.port}";
+            proxyWebsockets = lib.mkDefault true;
+            recommendedProxySettings = lib.mkDefault true;
+          };
+        }
+        (lib.mkIf (cfg.reverseProxy.ssl != null) { forceSSL = lib.mkDefault cfg.reverseProxy.ssl; })
+      ];
+    };
+  };
+
+  meta = {
+    maintainers = [ lib.maintainers.feathecutie ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/movim.nix b/nixos/modules/services/web-apps/movim.nix
index 29bed0e067fa4..51c3156fef063 100644
--- a/nixos/modules/services/web-apps/movim.nix
+++ b/nixos/modules/services/web-apps/movim.nix
@@ -86,8 +86,8 @@ let
                   # `cfg.podConfig` to prevent confusing situtions where the
                   # values are rewritten on server reboot
                   ''
-                    substituteInPlace ${appDir}/app/widgets/AdminMain/adminmain.tpl \
-                      --replace-warn 'name="${k}"' 'name="${k}" disabled'
+                    substituteInPlace ${appDir}/app/Widgets/AdminMain/adminmain.tpl \
+                      --replace-warn 'name="${k}"' 'name="${k}" readonly'
                   '')
               [ ]
               cfg.podConfig));
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index bfb3e73e65102..c8c4fe4b4d612 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -91,10 +91,10 @@ let
     cd ${webroot}
     sudo=exec
     if [[ "$USER" != nextcloud ]]; then
-      sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS'
+      sudo='exec /run/wrappers/bin/sudo -u nextcloud'
     fi
-    export NEXTCLOUD_CONFIG_DIR="${datadir}/config"
-    $sudo \
+    $sudo ${pkgs.coreutils}/bin/env \
+      NEXTCLOUD_CONFIG_DIR="${datadir}/config" \
       ${phpCli} \
       occ "$@"
   '';
diff --git a/nixos/modules/services/web-apps/onlyoffice.nix b/nixos/modules/services/web-apps/onlyoffice.nix
index 545ca68ccaac2..0d0e01d4f7bc0 100644
--- a/nixos/modules/services/web-apps/onlyoffice.nix
+++ b/nixos/modules/services/web-apps/onlyoffice.nix
@@ -1,24 +1,22 @@
 { lib, config, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.services.onlyoffice;
 in
 {
   options.services.onlyoffice = {
-    enable = mkEnableOption "OnlyOffice DocumentServer";
+    enable = lib.mkEnableOption "OnlyOffice DocumentServer";
 
-    enableExampleServer = mkEnableOption "OnlyOffice example server";
+    enableExampleServer = lib.mkEnableOption "OnlyOffice example server";
 
-    hostname = mkOption {
-      type = types.str;
+    hostname = lib.mkOption {
+      type = lib.types.str;
       default = "localhost";
-      description = "FQDN for the onlyoffice instance.";
+      description = "FQDN for the OnlyOffice instance.";
     };
 
-    jwtSecretFile = mkOption {
-      type = types.nullOr types.str;
+    jwtSecretFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
       default = null;
       description = ''
         Path to a file that contains the secret to sign web requests using JSON Web Tokens.
@@ -26,34 +24,34 @@ in
       '';
     };
 
-    package = mkPackageOption pkgs "onlyoffice-documentserver" { };
+    package = lib.mkPackageOption pkgs "onlyoffice-documentserver" { };
 
-    port = mkOption {
-      type = types.port;
+    port = lib.mkOption {
+      type = lib.types.port;
       default = 8000;
-      description = "Port the OnlyOffice DocumentServer should listens on.";
+      description = "Port the OnlyOffice document server should listen on.";
     };
 
-    examplePort = mkOption {
-      type = types.port;
+    examplePort = lib.mkOption {
+      type = lib.types.port;
       default = null;
-      description = "Port the OnlyOffice Example server should listens on.";
+      description = "Port the OnlyOffice example server should listen on.";
     };
 
-    postgresHost = mkOption {
-      type = types.str;
+    postgresHost = lib.mkOption {
+      type = lib.types.str;
       default = "/run/postgresql";
       description = "The Postgresql hostname or socket path OnlyOffice should connect to.";
     };
 
-    postgresName = mkOption {
-      type = types.str;
+    postgresName = lib.mkOption {
+      type = lib.types.str;
       default = "onlyoffice";
-      description = "The name of database OnlyOffice should user.";
+      description = "The name of database OnlyOffice should use.";
     };
 
-    postgresPasswordFile = mkOption {
-      type = types.nullOr types.str;
+    postgresPasswordFile = lib.mkOption {
+      type = lib.types.nullOr lib.types.str;
       default = null;
       description = ''
         Path to a file that contains the password OnlyOffice should use to connect to Postgresql.
@@ -61,8 +59,8 @@ in
       '';
     };
 
-    postgresUser = mkOption {
-      type = types.str;
+    postgresUser = lib.mkOption {
+      type = lib.types.str;
       default = "onlyoffice";
       description = ''
         The username OnlyOffice should use to connect to Postgresql.
@@ -70,8 +68,8 @@ in
       '';
     };
 
-    rabbitmqUrl = mkOption {
-      type = types.str;
+    rabbitmqUrl = lib.mkOption {
+      type = lib.types.str;
       default = "amqp://guest:guest@localhost:5672";
       description = "The Rabbitmq in amqp URI style OnlyOffice should connect to.";
     };
@@ -80,10 +78,10 @@ in
   config = lib.mkIf cfg.enable {
     services = {
       nginx = {
-        enable = mkDefault true;
+        enable = lib.mkDefault true;
         # misses text/csv, font/ttf, application/x-font-ttf, application/rtf, application/wasm
-        recommendedGzipSettings = mkDefault true;
-        recommendedProxySettings = mkDefault true;
+        recommendedGzipSettings = lib.mkDefault true;
+        recommendedProxySettings = lib.mkDefault true;
 
         upstreams = {
           # /etc/nginx/includes/http-common.conf
diff --git a/nixos/modules/services/web-apps/pretix.nix b/nixos/modules/services/web-apps/pretix.nix
index 0fb635964fe65..d298caab5b862 100644
--- a/nixos/modules/services/web-apps/pretix.nix
+++ b/nixos/modules/services/web-apps/pretix.nix
@@ -249,7 +249,7 @@ in
             };
 
             host = mkOption {
-              type = with types; nullOr types.path;
+              type = with types; nullOr path;
               default = if cfg.settings.database.backend == "postgresql" then "/run/postgresql" else null;
               defaultText = literalExpression ''
                 if config.services.pretix.settings..database.backend == "postgresql" then "/run/postgresql"
@@ -535,9 +535,10 @@ in
           fi
         '';
         serviceConfig = {
-          TimeoutStartSec = "5min";
+          TimeoutStartSec = "15min";
           ExecStart = "${getExe' pythonEnv "gunicorn"} --bind unix:/run/pretix/pretix.sock ${cfg.gunicorn.extraArgs} pretix.wsgi";
           RuntimeDirectory = "pretix";
+          Restart = "on-failure";
         };
       };
 
@@ -559,7 +560,10 @@ in
           "postgresql.service"
         ];
         wantedBy = [ "multi-user.target" ];
-        serviceConfig.ExecStart = "${getExe' pythonEnv "celery"} -A pretix.celery_app worker ${cfg.celery.extraArgs}";
+        serviceConfig = {
+          ExecStart = "${getExe' pythonEnv "celery"} -A pretix.celery_app worker ${cfg.celery.extraArgs}";
+          Restart = "on-failure";
+        };
       };
 
       nginx.serviceConfig.SupplementaryGroups = mkIf cfg.nginx.enable [ "pretix" ];
diff --git a/nixos/modules/services/web-apps/screego.nix b/nixos/modules/services/web-apps/screego.nix
new file mode 100644
index 0000000000000..9258e63c5d479
--- /dev/null
+++ b/nixos/modules/services/web-apps/screego.nix
@@ -0,0 +1,96 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  inherit (lib) mkOption types mkIf;
+  cfg = config.services.screego;
+  defaultSettings = {
+    SCREEGO_SERVER_ADDRESS = "127.0.0.1:5050";
+    SCREEGO_TURN_ADDRESS = "0.0.0.0:3478";
+    SCREEGO_TURN_PORT_RANGE = "50000:55000";
+    SCREEGO_SESSION_TIMEOUT_SECONDS = "0";
+    SCREEGO_CLOSE_ROOM_WHEN_OWNER_LEAVES = "true";
+    SCREEGO_AUTH_MODE = "turn";
+    SCREEGO_LOG_LEVEL = "info";
+  };
+in
+{
+  meta.maintainers = with lib.maintainers; [ pinpox ];
+
+  options.services.screego = {
+
+    enable = lib.mkEnableOption "screego screen-sharing server for developers";
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Open the firewall port(s).
+      '';
+    };
+
+    environmentFile = mkOption {
+      default = null;
+      description = ''
+        Environment file (see {manpage}`systemd.exec(5)` "EnvironmentFile="
+        section for the syntax) passed to the service. This option can be
+        used to safely include secrets in the configuration.
+      '';
+      example = "/run/secrets/screego-envfile";
+      type = with types; nullOr path;
+    };
+
+    settings = lib.mkOption {
+      type = types.attrsOf types.str;
+      description = ''
+        Screego settings passed as Nix attribute set, they will be merged with
+        the defaults. Settings will be passed as environment variables.
+
+        See https://screego.net/#/config for possible values
+      '';
+      default = defaultSettings;
+      example = {
+        SCREEGO_EXTERNAL_IP = "dns:example.com";
+      };
+    };
+  };
+
+  config =
+    let
+      # User-provided settings should be merged with default settings,
+      # overwriting where necessary
+      mergedConfig = defaultSettings // cfg.settings;
+      turnUDPPorts = lib.splitString ":" mergedConfig.SCREEGO_TURN_PORT_RANGE;
+      turnPort = lib.toInt (builtins.elemAt (lib.splitString ":" mergedConfig.SCREEGO_TURN_ADDRESS) 1);
+    in
+    mkIf (cfg.enable) {
+
+      networking.firewall = lib.mkIf cfg.openFirewall {
+        allowedTCPPorts = [ turnPort ];
+        allowedUDPPorts = [ turnPort ];
+        allowedUDPPortRanges = [
+          {
+            from = lib.toInt (builtins.elemAt turnUDPPorts 0);
+            to = lib.toInt (builtins.elemAt turnUDPPorts 1);
+          }
+        ];
+      };
+
+      systemd.services.screego = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        description = "screego screen-sharing for developers";
+        environment = mergedConfig;
+        serviceConfig = {
+          DynamicUser = true;
+          ExecStart = "${lib.getExe pkgs.screego} serve";
+          Restart = "on-failure";
+          RestartSec = "5s";
+        } // lib.optionalAttrs (cfg.environmentFile != null) { EnvironmentFile = cfg.environmentFile; };
+      };
+    };
+}
diff --git a/nixos/modules/services/web-apps/sogo.nix b/nixos/modules/services/web-apps/sogo.nix
index 78b577f18f282..1102418077664 100644
--- a/nixos/modules/services/web-apps/sogo.nix
+++ b/nixos/modules/services/web-apps/sogo.nix
@@ -2,17 +2,13 @@
   cfg = config.services.sogo;
 
   preStart = pkgs.writeShellScriptBin "sogo-prestart" ''
-    touch /etc/sogo/sogo.conf
-    chown sogo:sogo /etc/sogo/sogo.conf
-    chmod 640 /etc/sogo/sogo.conf
-
     ${if (cfg.configReplaces != {}) then ''
       # Insert secrets
       ${concatStringsSep "\n" (mapAttrsToList (k: v: ''export ${k}="$(cat "${v}" | tr -d '\n')"'') cfg.configReplaces)}
 
-      ${pkgs.perl}/bin/perl -p ${concatStringsSep " " (mapAttrsToList (k: v: '' -e 's/${k}/''${ENV{"${k}"}}/g;' '') cfg.configReplaces)} /etc/sogo/sogo.conf.raw > /etc/sogo/sogo.conf
+      ${pkgs.perl}/bin/perl -p ${concatStringsSep " " (mapAttrsToList (k: v: '' -e 's/${k}/''${ENV{"${k}"}}/g;' '') cfg.configReplaces)} /etc/sogo/sogo.conf.raw | install -m 640 -o sogo -g sogo /dev/stdin /etc/sogo/sogo.conf
     '' else ''
-      cp /etc/sogo/sogo.conf.raw /etc/sogo/sogo.conf
+      install -m 640 -o sogo -g sogo /etc/sogo/sogo.conf.raw /etc/sogo/sogo.conf
     ''}
   '';
 
diff --git a/nixos/modules/services/web-apps/stirling-pdf.nix b/nixos/modules/services/web-apps/stirling-pdf.nix
new file mode 100644
index 0000000000000..cd8638aff14d0
--- /dev/null
+++ b/nixos/modules/services/web-apps/stirling-pdf.nix
@@ -0,0 +1,110 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.stirling-pdf;
+in
+{
+  options.services.stirling-pdf = {
+    enable = lib.mkEnableOption "the stirling-pdf service";
+
+    package = lib.mkPackageOption pkgs "stirling-pdf" { };
+
+    environment = lib.mkOption {
+      type = lib.types.attrsOf (
+        lib.types.oneOf [
+          lib.types.str
+          lib.types.int
+        ]
+      );
+      default = { };
+      example = {
+        SERVER_PORT = 8080;
+        INSTALL_BOOK_AND_ADVANCED_HTML_OPS = "true";
+      };
+      description = ''
+        Environment variables for the stirling-pdf app.
+        See https://github.com/Stirling-Tools/Stirling-PDF#customisation for available options.
+      '';
+    };
+
+    environmentFiles = lib.mkOption {
+      type = lib.types.listOf lib.types.path;
+      default = [ ];
+      description = ''
+        Files containing additional environment variables to pass to Stirling PDF.
+        Secrets should be added in environmentFiles instead of environment.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.stirling-pdf = {
+      environment = lib.mapAttrs (_: toString) cfg.environment;
+
+      # following https://github.com/Stirling-Tools/Stirling-PDF#locally
+      path = with pkgs; [
+        unpaper
+        libreoffice
+        ocrmypdf
+        poppler_utils
+        unoconv
+        opencv
+        pngquant
+        tesseract
+        python3Packages.weasyprint
+        calibre
+      ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        BindReadOnlyPaths = [ "${pkgs.tesseract}/share/tessdata:/usr/share/tessdata" ];
+        CacheDirectory = "stirling-pdf";
+        Environment = [ "HOME=%S/stirling-pdf" ];
+        EnvironmentFile = cfg.environmentFiles;
+        ExecStart = lib.getExe cfg.package;
+        RuntimeDirectory = "stirling-pdf";
+        StateDirectory = "stirling-pdf";
+        SuccessExitStatus = 143;
+        User = "stirling-pdf";
+        WorkingDirectory = "/var/lib/stirling-pdf";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        DynamicUser = true;
+        LockPersonality = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "invisible";
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources @clock @setuid @chown"
+        ];
+        UMask = "0077";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ DCsunset ];
+}
diff --git a/nixos/modules/services/web-apps/weblate.nix b/nixos/modules/services/web-apps/weblate.nix
new file mode 100644
index 0000000000000..398d634a2b928
--- /dev/null
+++ b/nixos/modules/services/web-apps/weblate.nix
@@ -0,0 +1,388 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.services.weblate;
+
+  dataDir = "/var/lib/weblate";
+  settingsDir = "${dataDir}/settings";
+
+  finalPackage = cfg.package.overridePythonAttrs (old: {
+    # We only support the PostgreSQL backend in this module
+    dependencies = old.dependencies ++ cfg.package.optional-dependencies.postgres;
+    # Use a settings module in dataDir, to avoid having to rebuild the package
+    # when user changes settings.
+    makeWrapperArgs = (old.makeWrapperArgs or [ ]) ++ [
+      "--set PYTHONPATH  \"${settingsDir}\""
+      "--set DJANGO_SETTINGS_MODULE \"settings\""
+    ];
+  });
+  inherit (finalPackage) python;
+
+  pythonEnv = python.buildEnv.override {
+    extraLibs = with python.pkgs; [
+      (toPythonModule finalPackage)
+      celery
+    ];
+  };
+
+  # This extends and overrides the weblate/settings_example.py code found in upstream.
+  weblateConfig =
+    ''
+      # This was autogenerated by the NixOS module.
+
+      SITE_TITLE = "Weblate"
+      SITE_DOMAIN = "${cfg.localDomain}"
+      # TLS terminates at the reverse proxy, but this setting controls how links to weblate are generated.
+      ENABLE_HTTPS = True
+      SESSION_COOKIE_SECURE = ENABLE_HTTPS
+      DATA_DIR = "${dataDir}"
+      CACHE_DIR = f"{DATA_DIR}/cache"
+      STATIC_ROOT = "${finalPackage.static}/static"
+      MEDIA_ROOT = "/var/lib/weblate/media"
+      COMPRESS_ROOT = "${finalPackage.static}/compressor-cache"
+      DEBUG = False
+
+      DATABASES = {
+        "default": {
+          "ENGINE": "django.db.backends.postgresql",
+          "HOST": "/run/postgresql",
+          "NAME": "weblate",
+          "USER": "weblate",
+        }
+      }
+
+      with open("${cfg.djangoSecretKeyFile}") as f:
+        SECRET_KEY = f.read().rstrip("\n")
+
+      CACHES = {
+        "default": {
+          "BACKEND": "django_redis.cache.RedisCache",
+          "LOCATION": "unix://${config.services.redis.servers.weblate.unixSocket}",
+          "OPTIONS": {
+              "CLIENT_CLASS": "django_redis.client.DefaultClient",
+              "PASSWORD": None,
+              "CONNECTION_POOL_KWARGS": {},
+          },
+          "KEY_PREFIX": "weblate",
+          "TIMEOUT": 3600,
+        },
+        "avatar": {
+          "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
+          "LOCATION": "/var/lib/weblate/avatar-cache",
+          "TIMEOUT": 86400,
+          "OPTIONS": {"MAX_ENTRIES": 1000},
+        }
+      }
+
+
+      CELERY_TASK_ALWAYS_EAGER = False
+      CELERY_BROKER_URL = "redis+socket://${config.services.redis.servers.weblate.unixSocket}"
+      CELERY_RESULT_BACKEND = CELERY_BROKER_URL
+
+      VCS_BACKENDS = ("weblate.vcs.git.GitRepository",)
+
+    ''
+    + lib.optionalString cfg.smtp.enable ''
+      ADMINS = (("Weblate Admin", "${cfg.smtp.user}"),)
+
+      EMAIL_HOST = "${cfg.smtp.host}"
+      EMAIL_USE_TLS = True
+      EMAIL_HOST_USER = "${cfg.smtp.user}"
+      SERVER_EMAIL = "${cfg.smtp.user}"
+      DEFAULT_FROM_EMAIL = "${cfg.smtp.user}"
+      EMAIL_PORT = 587
+      with open("${cfg.smtp.passwordFile}") as f:
+        EMAIL_HOST_PASSWORD = f.read().rstrip("\n")
+
+    ''
+    + cfg.extraConfig;
+  settings_py =
+    pkgs.runCommand "weblate_settings.py"
+      {
+        inherit weblateConfig;
+        passAsFile = [ "weblateConfig" ];
+      }
+      ''
+        mkdir -p $out
+        cat \
+          ${finalPackage}/${python.sitePackages}/weblate/settings_example.py \
+          $weblateConfigPath \
+          > $out/settings.py
+      '';
+
+  environment = {
+    PYTHONPATH = "${settingsDir}:${pythonEnv}/${python.sitePackages}/";
+    DJANGO_SETTINGS_MODULE = "settings";
+    # We run Weblate through gunicorn, so we can't utilise the env var set in the wrapper.
+    inherit (finalPackage) GI_TYPELIB_PATH;
+  };
+
+  weblatePath = with pkgs; [
+    gitSVN
+
+    #optional
+    git-review
+    tesseract
+    licensee
+    mercurial
+  ];
+in
+{
+
+  options = {
+    services.weblate = {
+      enable = lib.mkEnableOption "Weblate service";
+
+      package = lib.mkPackageOption pkgs "weblate" { };
+
+      localDomain = lib.mkOption {
+        description = "The domain name serving your Weblate instance.";
+        example = "weblate.example.org";
+        type = lib.types.str;
+      };
+
+      djangoSecretKeyFile = lib.mkOption {
+        description = ''
+          Location of the Django secret key.
+
+          This should be a path pointing to a file with secure permissions (not /nix/store).
+
+          Can be generated with `weblate-generate-secret-key` which is available as the `weblate` user.
+        '';
+        type = lib.types.path;
+      };
+
+      extraConfig = lib.mkOption {
+        type = lib.types.lines;
+        default = "";
+        description = ''
+          Text to append to `settings.py` Weblate configuration file.
+        '';
+      };
+
+      smtp = {
+        enable = lib.mkEnableOption "Weblate SMTP support";
+        user = lib.mkOption {
+          description = "SMTP login name.";
+          example = "weblate@example.org";
+          type = lib.types.str;
+        };
+
+        host = lib.mkOption {
+          description = "SMTP host used when sending emails to users.";
+          type = lib.types.str;
+          example = "127.0.0.1";
+        };
+
+        passwordFile = lib.mkOption {
+          description = ''
+            Location of a file containing the SMTP password.
+
+            This should be a path pointing to a file with secure permissions (not /nix/store).
+          '';
+          type = lib.types.path;
+        };
+      };
+
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [ "L+ ${settingsDir} - - - - ${settings_py}" ];
+
+    services.nginx = {
+      enable = true;
+      virtualHosts."${cfg.localDomain}" = {
+
+        forceSSL = true;
+        enableACME = true;
+
+        locations = {
+          "= /favicon.ico".alias = "${finalPackage}/${python.sitePackages}/weblate/static/favicon.ico";
+          "/static/".alias = "${finalPackage.static}/static/";
+          "/static/CACHE/".alias = "${finalPackage.static}/compressor-cache/CACHE/";
+          "/media/".alias = "/var/lib/weblate/media/";
+          "/".proxyPass = "http://unix:///run/weblate.socket";
+        };
+
+      };
+    };
+
+    systemd.services.weblate-postgresql-setup = {
+      description = "Weblate PostgreSQL setup";
+      after = [ "postgresql.service" ];
+      serviceConfig = {
+        Type = "oneshot";
+        User = "postgres";
+        Group = "postgres";
+        ExecStart = ''
+          ${config.services.postgresql.package}/bin/psql weblate -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+        '';
+      };
+    };
+
+    systemd.services.weblate-migrate = {
+      description = "Weblate migration";
+      after = [ "weblate-postgresql-setup.service" ];
+      requires = [ "weblate-postgresql-setup.service" ];
+      # We want this to be active on boot, not just on socket activation
+      wantedBy = [ "multi-user.target" ];
+      inherit environment;
+      path = weblatePath;
+      serviceConfig = {
+        Type = "oneshot";
+        StateDirectory = "weblate";
+        User = "weblate";
+        Group = "weblate";
+        ExecStart = "${finalPackage}/bin/weblate migrate --noinput";
+      };
+    };
+
+    systemd.services.weblate-celery = {
+      description = "Weblate Celery";
+      after = [
+        "network.target"
+        "redis.service"
+        "postgresql.service"
+      ];
+      # We want this to be active on boot, not just on socket activation
+      wantedBy = [ "multi-user.target" ];
+      environment = environment // {
+        CELERY_WORKER_RUNNING = "1";
+      };
+      path = weblatePath;
+      # Recommendations from:
+      # https://github.com/WeblateOrg/weblate/blob/main/weblate/examples/celery-weblate.service
+      serviceConfig =
+        let
+          # We have to push %n through systemd's replacement, therefore %%n.
+          pidFile = "/run/celery/weblate-%%n.pid";
+          nodes = "celery notify memory backup translate";
+          cmd = verb: ''
+            ${pythonEnv}/bin/celery multi ${verb} \
+              ${nodes} \
+              -A "weblate.utils" \
+              --pidfile=${pidFile} \
+              --logfile=/var/log/celery/weblate-%%n%%I.log \
+              --loglevel=DEBUG \
+              --beat:celery \
+              --queues:celery=celery \
+              --prefetch-multiplier:celery=4 \
+              --queues:notify=notify \
+              --prefetch-multiplier:notify=10 \
+              --queues:memory=memory \
+              --prefetch-multiplier:memory=10 \
+              --queues:translate=translate \
+              --prefetch-multiplier:translate=4 \
+              --concurrency:backup=1 \
+              --queues:backup=backup \
+              --prefetch-multiplier:backup=2
+          '';
+        in
+        {
+          Type = "forking";
+          User = "weblate";
+          Group = "weblate";
+          WorkingDirectory = "${finalPackage}/${python.sitePackages}/weblate/";
+          RuntimeDirectory = "celery";
+          RuntimeDirectoryPreserve = "restart";
+          LogsDirectory = "celery";
+          ExecStart = cmd "start";
+          ExecReload = cmd "restart";
+          ExecStop = ''
+            ${pythonEnv}/bin/celery multi stopwait \
+              ${nodes} \
+              --pidfile=${pidFile}
+          '';
+          Restart = "always";
+        };
+    };
+
+    systemd.services.weblate = {
+      description = "Weblate Gunicorn app";
+      after = [
+        "network.target"
+        "weblate-migrate.service"
+        "weblate-celery.service"
+      ];
+      requires = [
+        "weblate-migrate.service"
+        "weblate-celery.service"
+        "weblate.socket"
+      ];
+      inherit environment;
+      path = weblatePath;
+      serviceConfig = {
+        Type = "notify";
+        NotifyAccess = "all";
+        ExecStart =
+          let
+            gunicorn = python.pkgs.gunicorn.overridePythonAttrs (old: {
+              # Allows Gunicorn to set a meaningful process name
+              dependencies = (old.dependencies or [ ]) ++ old.optional-dependencies.setproctitle;
+            });
+          in
+          ''
+            ${gunicorn}/bin/gunicorn \
+              --name=weblate \
+              --bind='unix:///run/weblate.socket' \
+              weblate.wsgi
+          '';
+        ExecReload = "kill -s HUP $MAINPID";
+        KillMode = "mixed";
+        PrivateTmp = true;
+        WorkingDirectory = dataDir;
+        StateDirectory = "weblate";
+        RuntimeDirectory = "weblate";
+        User = "weblate";
+        Group = "weblate";
+      };
+    };
+
+    systemd.sockets.weblate = {
+      before = [ "nginx.service" ];
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream = "/run/weblate.socket";
+        SocketUser = "weblate";
+        SocketGroup = "weblate";
+        SocketMode = "770";
+      };
+    };
+
+    services.redis.servers.weblate = {
+      enable = true;
+      user = "weblate";
+      unixSocket = "/run/redis-weblate/redis.sock";
+      unixSocketPerm = 770;
+    };
+
+    services.postgresql = {
+      enable = true;
+      ensureUsers = [
+        {
+          name = "weblate";
+          ensureDBOwnership = true;
+        }
+      ];
+      ensureDatabases = [ "weblate" ];
+    };
+
+    users.users.weblate = {
+      isSystemUser = true;
+      group = "weblate";
+      packages = [ finalPackage ] ++ weblatePath;
+    };
+
+    users.groups.weblate.members = [ config.services.nginx.user ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ erictapen ];
+
+}
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
index 0d49f2d92998c..37c39608edae1 100644
--- a/nixos/modules/services/web-apps/wordpress.nix
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -70,7 +70,7 @@ let
         require_once(ABSPATH . 'wp-settings.php');
       ?>
     '';
-    checkPhase = "${pkgs.php81}/bin/php --syntax-check $target";
+    checkPhase = "${pkgs.php}/bin/php --syntax-check $target";
   };
 
   mkPhpValue = v: let
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
index 2455e676e583a..3463148b30e0d 100644
--- a/nixos/modules/services/web-apps/zabbix.nix
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -1,9 +1,31 @@
-{ config, lib, options, pkgs, ... }:
+{
+  config,
+  lib,
+  options,
+  pkgs,
+  ...
+}:
 
 let
 
-  inherit (lib) mkDefault mkEnableOption mkPackageOption mkForce mkIf mkMerge mkOption types;
-  inherit (lib) literalExpression mapAttrs optionalString versionAtLeast;
+  inherit (lib)
+    mkDefault
+    mkEnableOption
+    mkPackageOption
+    mkRenamedOptionModule
+    mkForce
+    mkIf
+    mkMerge
+    mkOption
+    types
+    ;
+  inherit (lib)
+    literalExpression
+    mapAttrs
+    optionalString
+    optionals
+    versionAtLeast
+    ;
 
   cfg = config.services.zabbixWeb;
   opt = options.services.zabbixWeb;
@@ -17,13 +39,25 @@ let
     <?php
     // Zabbix GUI configuration file.
     global $DB;
-    $DB['TYPE'] = '${ { mysql = "MYSQL"; pgsql = "POSTGRESQL"; oracle = "ORACLE"; }.${cfg.database.type} }';
+    $DB['TYPE'] = '${
+      {
+        mysql = "MYSQL";
+        pgsql = "POSTGRESQL";
+        oracle = "ORACLE";
+      }
+      .${cfg.database.type}
+    }';
     $DB['SERVER'] = '${cfg.database.host}';
     $DB['PORT'] = '${toString cfg.database.port}';
     $DB['DATABASE'] = '${cfg.database.name}';
     $DB['USER'] = '${cfg.database.user}';
     # NOTE: file_get_contents adds newline at the end of returned string
-    $DB['PASSWORD'] = ${if cfg.database.passwordFile != null then "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")" else "''"};
+    $DB['PASSWORD'] = ${
+      if cfg.database.passwordFile != null then
+        "trim(file_get_contents('${cfg.database.passwordFile}'), \"\\r\\n\")"
+      else
+        "''"
+    };
     // Schema name. Used for IBM DB2 and PostgreSQL.
     $DB['SCHEMA'] = ''';
     $ZBX_SERVER = '${cfg.server.address}';
@@ -33,16 +67,33 @@ let
 
     ${cfg.extraConfig}
   '';
-
 in
 {
+  imports = [
+    (mkRenamedOptionModule
+      [
+        "services"
+        "zabbixWeb"
+        "virtualHost"
+      ]
+      [
+        "services"
+        "zabbixWeb"
+        "httpd"
+        "virtualHost"
+      ]
+    )
+  ];
   # interface
 
   options.services = {
     zabbixWeb = {
       enable = mkEnableOption "the Zabbix web interface";
 
-      package = mkPackageOption pkgs [ "zabbix" "web" ] { };
+      package = mkPackageOption pkgs [
+        "zabbix"
+        "web"
+      ] { };
 
       server = {
         port = mkOption {
@@ -60,7 +111,11 @@ in
 
       database = {
         type = mkOption {
-          type = types.enum [ "mysql" "pgsql" "oracle" ];
+          type = types.enum [
+            "mysql"
+            "pgsql"
+            "oracle"
+          ];
           example = "mysql";
           default = "pgsql";
           description = "Database engine to use.";
@@ -75,9 +130,12 @@ in
         port = mkOption {
           type = types.port;
           default =
-            if cfg.database.type == "mysql" then config.services.mysql.port
-            else if cfg.database.type == "pgsql" then config.services.postgresql.settings.port
-            else 1521;
+            if cfg.database.type == "mysql" then
+              config.services.mysql.port
+            else if cfg.database.type == "pgsql" then
+              config.services.postgresql.settings.port
+            else
+              1521;
           defaultText = literalExpression ''
             if config.${opt.database.type} == "mysql" then config.${options.services.mysql.port}
             else if config.${opt.database.type} == "pgsql" then config.services.postgresql.settings.port
@@ -116,7 +174,17 @@ in
         };
       };
 
-      virtualHost = mkOption {
+      frontend = mkOption {
+        type = types.enum [
+          "nginx"
+          "httpd"
+        ];
+        example = "nginx";
+        default = "httpd";
+        description = "Frontend server to use.";
+      };
+
+      httpd.virtualHost = mkOption {
         type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
         example = literalExpression ''
           {
@@ -126,14 +194,43 @@ in
             enableACME = true;
           }
         '';
+        default = { };
         description = ''
           Apache configuration can be done by adapting `services.httpd.virtualHosts.<name>`.
           See [](#opt-services.httpd.virtualHosts) for further information.
         '';
       };
 
+      hostname = mkOption {
+        type = types.str;
+        default = "zabbix.local";
+        description = "Hostname for either nginx or httpd.";
+      };
+
+      nginx.virtualHost = mkOption {
+        type = types.submodule (import ../web-servers/nginx/vhost-options.nix);
+        example = literalExpression ''
+          {
+            forceSSL = true;
+            sslCertificateKey = "/etc/ssl/zabbix.key";
+            sslCertificate = "/etc/ssl/zabbix.crt";
+          }
+        '';
+        default = { };
+        description = ''
+          Nginx configuration can be done by adapting `services.nginx.virtualHosts.<name>`.
+          See [](#opt-services.nginx.virtualHosts) for further information.
+        '';
+      };
+
       poolConfig = mkOption {
-        type = with types; attrsOf (oneOf [ str int bool ]);
+        type =
+          with types;
+          attrsOf (oneOf [
+            str
+            int
+            bool
+          ]);
         default = {
           "pm" = "dynamic";
           "pm.max_children" = 32;
@@ -154,7 +251,6 @@ in
           Additional configuration to be copied verbatim into {file}`zabbix.conf.php`.
         '';
       };
-
     };
   };
 
@@ -162,61 +258,96 @@ in
 
   config = mkIf cfg.enable {
 
-    services.zabbixWeb.extraConfig = optionalString ((versionAtLeast config.system.stateVersion "20.09") && (versionAtLeast cfg.package.version "5.0.0")) ''
-      $DB['DOUBLE_IEEE754'] = 'true';
-    '';
+    services.zabbixWeb.extraConfig =
+      optionalString
+        (
+          (versionAtLeast config.system.stateVersion "20.09") && (versionAtLeast cfg.package.version "5.0.0")
+        )
+        ''
+          $DB['DOUBLE_IEEE754'] = 'true';
+        '';
 
-    systemd.tmpfiles.rules = [
-      "d '${stateDir}' 0750 ${user} ${group} - -"
-      "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
-    ];
+    systemd.tmpfiles.rules =
+      [ "d '${stateDir}' 0750 ${user} ${group} - -" ]
+      ++ optionals (cfg.frontend == "httpd") [
+        "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
+      ]
+      ++ optionals (cfg.frontend == "nginx") [
+        "d '${stateDir}/session' 0750 ${user} ${config.services.nginx.group} - -"
+      ];
 
     services.phpfpm.pools.zabbix = {
       inherit user;
-      group = config.services.httpd.group;
-      phpOptions = ''
-        # https://www.zabbix.com/documentation/current/manual/installation/install
-        memory_limit = 128M
-        post_max_size = 16M
-        upload_max_filesize = 2M
-        max_execution_time = 300
-        max_input_time = 300
-        session.auto_start = 0
-        mbstring.func_overload = 0
-        always_populate_raw_post_data = -1
-        # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
-        session.save_path = ${stateDir}/session
-      '' + optionalString (config.time.timeZone != null) ''
-        date.timezone = "${config.time.timeZone}"
-      '' + optionalString (cfg.database.type == "oracle") ''
-        extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
-      '';
+      group = config.services.${cfg.frontend}.group;
+      phpOptions =
+        ''
+          # https://www.zabbix.com/documentation/current/manual/installation/install
+          memory_limit = 128M
+          post_max_size = 16M
+          upload_max_filesize = 2M
+          max_execution_time = 300
+          max_input_time = 300
+          session.auto_start = 0
+          mbstring.func_overload = 0
+          always_populate_raw_post_data = -1
+          # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
+          session.save_path = ${stateDir}/session
+        ''
+        + optionalString (config.time.timeZone != null) ''
+          date.timezone = "${config.time.timeZone}"
+        ''
+        + optionalString (cfg.database.type == "oracle") ''
+          extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
+        '';
       phpEnv.ZABBIX_CONFIG = "${zabbixConfig}";
       settings = {
-        "listen.owner" = config.services.httpd.user;
-        "listen.group" = config.services.httpd.group;
+        "listen.owner" =
+          if cfg.frontend == "httpd" then config.services.httpd.user else config.services.nginx.user;
+        "listen.group" =
+          if cfg.frontend == "httpd" then config.services.httpd.group else config.services.nginx.group;
       } // cfg.poolConfig;
     };
 
-    services.httpd = {
+    services.httpd = mkIf (cfg.frontend == "httpd") {
       enable = true;
-      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      adminAddr = mkDefault cfg.httpd.virtualHost.adminAddr;
       extraModules = [ "proxy_fcgi" ];
-      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
-        documentRoot = mkForce "${cfg.package}/share/zabbix";
-        extraConfig = ''
-          <Directory "${cfg.package}/share/zabbix">
-            <FilesMatch "\.php$">
-              <If "-f %{REQUEST_FILENAME}">
-                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
-              </If>
-            </FilesMatch>
-            AllowOverride all
-            Options -Indexes
-            DirectoryIndex index.php
-          </Directory>
-        '';
-      } ];
+      virtualHosts.${cfg.hostname} = mkMerge [
+        cfg.httpd.virtualHost
+        {
+          documentRoot = mkForce "${cfg.package}/share/zabbix";
+          extraConfig = ''
+            <Directory "${cfg.package}/share/zabbix">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+                </If>
+              </FilesMatch>
+              AllowOverride all
+              Options -Indexes
+              DirectoryIndex index.php
+            </Directory>
+          '';
+        }
+      ];
+    };
+
+    services.nginx = mkIf (cfg.frontend == "nginx") {
+      enable = true;
+      virtualHosts.${cfg.hostname} = mkMerge [
+        cfg.nginx.virtualHost
+        {
+          root = mkForce "${cfg.package}/share/zabbix";
+          locations."/" = {
+            index = "index.html index.htm index.php";
+            tryFiles = "$uri $uri/ =404";
+          };
+          locations."~ \.php$".extraConfig = ''
+            fastcgi_pass  unix:${fpm.socket};
+            fastcgi_index index.php;
+          '';
+        }
+      ];
     };
 
     users.users.${user} = mapAttrs (name: mkDefault) {
@@ -225,9 +356,6 @@ in
       inherit group;
     };
 
-    users.groups.${group} = mapAttrs (name: mkDefault) {
-      gid = config.ids.gids.zabbix;
-    };
-
+    users.groups.${group} = mapAttrs (name: mkDefault) { gid = config.ids.gids.zabbix; };
   };
 }
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 5dd28a1db00ef..46cb099595794 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -538,25 +538,13 @@ in
         '';
       };
 
-      enableMellon = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable the mod_auth_mellon module.";
-      };
+      enableMellon = mkEnableOption "the mod_auth_mellon module";
 
-      enablePHP = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable the PHP module.";
-      };
+      enablePHP = mkEnableOption "the PHP module";
 
       phpPackage = mkPackageOption pkgs "php" { };
 
-      enablePerl = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to enable the Perl module (mod_perl).";
-      };
+      enablePerl = mkEnableOption "the Perl module (mod_perl)";
 
       phpOptions = mkOption {
         type = types.lines;
diff --git a/nixos/modules/services/web-servers/caddy/default.nix b/nixos/modules/services/web-servers/caddy/default.nix
index 064a0c71b586b..59c0ab13de19b 100644
--- a/nixos/modules/services/web-servers/caddy/default.nix
+++ b/nixos/modules/services/web-servers/caddy/default.nix
@@ -84,12 +84,12 @@ in
       default = "caddy";
       type = types.str;
       description = ''
-        Group account under which caddy runs.
+        Group under which caddy runs.
 
         ::: {.note}
-        If left as the default value this user will automatically be created
+        If left as the default value this group will automatically be created
         on system activation, otherwise you are responsible for
-        ensuring the user exists before the Caddy service starts.
+        ensuring the group exists before the Caddy service starts.
         :::
       '';
     };
@@ -242,7 +242,7 @@ in
             serverAliases = [ "www.hydra.example.com" ];
             extraConfig = '''
               encode gzip
-              root /srv/http
+              root * /srv/http
             ''';
           };
         };
diff --git a/nixos/modules/services/web-servers/fcgiwrap.nix b/nixos/modules/services/web-servers/fcgiwrap.nix
index 29ddd39942c60..36a327b9ab9f9 100644
--- a/nixos/modules/services/web-servers/fcgiwrap.nix
+++ b/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -3,12 +3,26 @@
 with lib;
 
 let
-  forEachInstance = f: flip mapAttrs' config.services.fcgiwrap (name: cfg:
-    nameValuePair "fcgiwrap-${name}" (f cfg)
+  forEachInstance = f: flip mapAttrs' config.services.fcgiwrap.instances (
+    name: cfg: nameValuePair "fcgiwrap-${name}" (f cfg)
   );
 
 in {
-  options.services.fcgiwrap = mkOption {
+  imports = forEach [
+    "enable"
+    "user"
+    "group"
+    "socketType"
+    "socketAddress"
+    "preforkProcesses"
+  ] (attr: mkRemovedOptionModule [ "services" "fcgiwrap" attr ] ''
+      The global shared fcgiwrap instance is no longer supported due to
+      security issues.
+      Isolated instances should instead be configured through
+      `services.fcgiwrap.instances.*'.
+  '');
+
+  options.services.fcgiwrap.instances = mkOption {
     description = "Configuration for fcgiwrap instances.";
     default = { };
     type = types.attrsOf (types.submodule ({ config, ... }: { options = {
@@ -54,7 +68,6 @@ in {
         default = null;
         description = ''
           User to be set as owner of the UNIX socket.
-          Defaults to the process running user.
         '';
       };
 
@@ -63,7 +76,6 @@ in {
         default = null;
         description = ''
           Group to be set as owner of the UNIX socket.
-          Defaults to the process running group.
         '';
       };
 
@@ -84,6 +96,14 @@ in {
   config = {
     assertions = concatLists (mapAttrsToList (name: cfg: [
       {
+        assertion = cfg.socket.type == "unix" -> cfg.socket.user != null;
+        message = "Socket owner is required for the UNIX socket type.";
+      }
+      {
+        assertion = cfg.socket.type == "unix" -> cfg.socket.group != null;
+        message = "Socket owner is required for the UNIX socket type.";
+      }
+      {
         assertion = cfg.socket.user != null -> cfg.socket.type == "unix";
         message = "Socket owner can only be set for the UNIX socket type.";
       }
@@ -95,7 +115,7 @@ in {
         assertion = cfg.socket.mode != null -> cfg.socket.type == "unix";
         message = "Socket mode can only be set for the UNIX socket type.";
       }
-    ]) config.services.fcgiwrap);
+    ]) config.services.fcgiwrap.instances);
 
     systemd.services = forEachInstance (cfg: {
       after = [ "nss-user-lookup.target" ];
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
deleted file mode 100644
index 68c0859fc3322..0000000000000
--- a/nixos/modules/services/web-servers/hydron.nix
+++ /dev/null
@@ -1,164 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-let
-  cfg = config.services.hydron;
-in with lib; {
-  options.services.hydron = {
-    enable = mkEnableOption "hydron";
-
-    dataDir = mkOption {
-      type = types.path;
-      default = "/var/lib/hydron";
-      example = "/home/okina/hydron";
-      description = "Location where hydron runs and stores data.";
-    };
-
-    interval = mkOption {
-      type = types.str;
-      default = "weekly";
-      example = "06:00";
-      description = ''
-        How often we run hydron import and possibly fetch tags. Runs by default every week.
-
-        The format is described in
-        {manpage}`systemd.time(7)`.
-      '';
-    };
-
-    password = mkOption {
-      type = types.str;
-      default = "hydron";
-      example = "dumbpass";
-      description = "Password for the hydron database.";
-    };
-
-    passwordFile = mkOption {
-      type = types.path;
-      default = "/run/keys/hydron-password-file";
-      example = "/home/okina/hydron/keys/pass";
-      description = "Password file for the hydron database.";
-    };
-
-    postgresArgs = mkOption {
-      type = types.str;
-      description = "Postgresql connection arguments.";
-      example = ''
-        {
-          "driver": "postgres",
-          "connection": "user=hydron password=dumbpass dbname=hydron sslmode=disable"
-        }
-      '';
-    };
-
-    postgresArgsFile = mkOption {
-      type = types.path;
-      default = "/run/keys/hydron-postgres-args";
-      example = "/home/okina/hydron/keys/postgres";
-      description = "Postgresql connection arguments file.";
-    };
-
-    listenAddress = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      example = "127.0.0.1:8010";
-      description = "Listen on a specific IP address and port.";
-    };
-
-    importPaths = mkOption {
-      type = types.listOf types.path;
-      default = [];
-      example = [ "/home/okina/Pictures" ];
-      description = "Paths that hydron will recursively import.";
-    };
-
-    fetchTags = mkOption {
-      type = types.bool;
-      default = true;
-      description = "Fetch tags for imported images and webm from gelbooru.";
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.hydron.passwordFile = mkDefault (pkgs.writeText "hydron-password-file" cfg.password);
-    services.hydron.postgresArgsFile = mkDefault (pkgs.writeText "hydron-postgres-args" cfg.postgresArgs);
-    services.hydron.postgresArgs = mkDefault ''
-      {
-        "driver": "postgres",
-        "connection": "user=hydron password=${cfg.password} host=/run/postgresql dbname=hydron sslmode=disable"
-      }
-    '';
-
-    services.postgresql = {
-      enable = true;
-      ensureDatabases = [ "hydron" ];
-      ensureUsers = [
-        { name = "hydron";
-          ensureDBOwnership = true;
-        }
-      ];
-    };
-
-    systemd.tmpfiles.rules = [
-      "d '${cfg.dataDir}' 0750 hydron hydron - -"
-      "d '${cfg.dataDir}/.hydron' - hydron hydron - -"
-      "d '${cfg.dataDir}/images' - hydron hydron - -"
-      "Z '${cfg.dataDir}' - hydron hydron - -"
-
-      "L+ '${cfg.dataDir}/.hydron/db_conf.json' - - - - ${cfg.postgresArgsFile}"
-    ];
-
-    systemd.services.hydron = {
-      description = "hydron";
-      after = [ "network.target" "postgresql.service" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        User = "hydron";
-        Group = "hydron";
-        ExecStart = "${pkgs.hydron}/bin/hydron serve"
-        + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}";
-      };
-    };
-
-    systemd.services.hydron-fetch = {
-      description = "Import paths into hydron and possibly fetch tags";
-
-      serviceConfig = {
-        Type = "oneshot";
-        User = "hydron";
-        Group = "hydron";
-        ExecStart = "${pkgs.hydron}/bin/hydron import "
-        + optionalString cfg.fetchTags "-f "
-        + (escapeShellArg cfg.dataDir) + "/images " + (escapeShellArgs cfg.importPaths);
-      };
-    };
-
-    systemd.timers.hydron-fetch = {
-      description = "Automatically import paths into hydron and possibly fetch tags";
-      after = [ "network.target" "hydron.service" ];
-      wantedBy = [ "timers.target" ];
-
-      timerConfig = {
-        Persistent = true;
-        OnCalendar = cfg.interval;
-      };
-    };
-
-    users = {
-      groups.hydron.gid = config.ids.gids.hydron;
-
-      users.hydron = {
-        description = "hydron server service user";
-        home = cfg.dataDir;
-        group = "hydron";
-        uid = config.ids.uids.hydron;
-      };
-    };
-  };
-
-  imports = [
-    (mkRenamedOptionModule [ "services" "hydron" "baseDir" ] [ "services" "hydron" "dataDir" ])
-  ];
-
-  meta.maintainers = with maintainers; [ Madouura ];
-}
diff --git a/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixos/modules/services/web-servers/nginx/gitweb.nix
index 9242c1adbde16..81f794bf9b3f8 100644
--- a/nixos/modules/services/web-servers/nginx/gitweb.nix
+++ b/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -89,6 +89,6 @@ in
 
   };
 
-  meta.maintainers = with maintainers; [ ];
+  meta.maintainers = [ ];
 
 }
diff --git a/nixos/modules/services/x11/desktop-managers/budgie.nix b/nixos/modules/services/x11/desktop-managers/budgie.nix
index 7f43a939970b8..987f114ec5e18 100644
--- a/nixos/modules/services/x11/desktop-managers/budgie.nix
+++ b/nixos/modules/services/x11/desktop-managers/budgie.nix
@@ -8,7 +8,7 @@ let
   nixos-background-light = pkgs.nixos-artwork.wallpapers.nineish;
   nixos-background-dark = pkgs.nixos-artwork.wallpapers.nineish-dark-gray;
 
-  nixos-gsettings-overrides = pkgs.budgie.budgie-gsettings-overrides.override {
+  nixos-gsettings-overrides = pkgs.budgie-gsettings-overrides.override {
     inherit (cfg) extraGSettingsOverrides extraGSettingsOverridePackages;
     inherit nixos-background-dark nixos-background-light;
   };
@@ -40,7 +40,7 @@ let
     destination = "/share/gnome-background-properties/nixos.xml";
   };
 
-  budgie-control-center = pkgs.budgie.budgie-control-center.override {
+  budgie-control-center' = pkgs.budgie-control-center.override {
     enableSshSocket = config.services.openssh.startWhenNeeded;
   };
 
@@ -80,7 +80,7 @@ in {
         description = "Extra plugins for the Budgie desktop";
         type = types.listOf types.package;
         default = [];
-        example = literalExpression "[ pkgs.budgiePlugins.budgie-analogue-clock-applet ]";
+        example = literalExpression "[ pkgs.budgie-analogue-clock-applet ]";
       };
     };
 
@@ -94,7 +94,7 @@ in {
 
   config = mkIf cfg.enable {
     services.displayManager.sessionPackages = with pkgs; [
-      budgie.budgie-desktop
+      budgie-desktop
     ];
 
     services.xserver.displayManager.lightdm.greeters.slick = {
@@ -104,7 +104,7 @@ in {
       cursorTheme = mkDefault { name = "Qogir"; package = pkgs.qogir-icon-theme; };
     };
 
-    services.xserver.desktopManager.budgie.sessionPath = [ pkgs.budgie.budgie-desktop-view ];
+    services.xserver.desktopManager.budgie.sessionPath = [ pkgs.budgie-desktop-view ];
 
     environment.extraInit = ''
       ${concatMapStrings (p: ''
@@ -121,12 +121,12 @@ in {
     environment.systemPackages = with pkgs;
       [
         # Budgie Desktop.
-        budgie.budgie-backgrounds
-        budgie-control-center
-        (budgie.budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
-        budgie.budgie-desktop-view
-        budgie.budgie-screensaver
-        budgie.budgie-session
+        budgie-backgrounds
+        budgie-control-center'
+        (budgie-desktop-with-plugins.override { plugins = cfg.extraPlugins; })
+        budgie-desktop-view
+        budgie-screensaver
+        budgie-session
 
         # Required by Budgie Menu.
         gnome-menus
@@ -142,7 +142,7 @@ in {
       ]
       ++ lib.optional config.networking.networkmanager.enable pkgs.networkmanagerapplet
       ++ (utils.removePackagesByName [
-          cinnamon.nemo
+          nemo
           mate.eom
           mate.pluma
           mate.atril
@@ -210,7 +210,7 @@ in {
     xdg.portal.extraPortals = with pkgs; [
       xdg-desktop-portal-gtk # provides a XDG Portals implementation.
     ];
-    xdg.portal.configPackages = mkDefault [ pkgs.budgie.budgie-desktop ];
+    xdg.portal.configPackages = mkDefault [ pkgs.budgie-desktop ];
 
     services.geoclue2.enable = mkDefault true; # for BCC's Privacy > Location Services panel.
     services.upower.enable = config.powerManagement.enable; # for Budgie's Status Indicator and BCC's Power panel.
@@ -243,12 +243,12 @@ in {
 
     # Register packages for DBus.
     services.dbus.packages = [
-      budgie-control-center
+      budgie-control-center'
     ];
 
     # Register packages for udev.
     services.udev.packages = with pkgs; [
-      budgie.magpie
+      magpie
     ];
 
     # Shell integration for MATE Terminal.
diff --git a/nixos/modules/services/x11/desktop-managers/cinnamon.nix b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
index 0dc21862d8348..70fa4f0f3a5a4 100644
--- a/nixos/modules/services/x11/desktop-managers/cinnamon.nix
+++ b/nixos/modules/services/x11/desktop-managers/cinnamon.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.xserver.desktopManager.cinnamon;
   serviceCfg = config.services.cinnamon;
 
-  nixos-gsettings-overrides = pkgs.cinnamon.cinnamon-gsettings-overrides.override {
+  nixos-gsettings-overrides = pkgs.cinnamon-gsettings-overrides.override {
     extraGSettingsOverridePackages = cfg.extraGSettingsOverridePackages;
     extraGSettingsOverrides = cfg.extraGSettingsOverrides;
   };
@@ -51,7 +51,7 @@ in
 
     environment.cinnamon.excludePackages = mkOption {
       default = [];
-      example = literalExpression "[ pkgs.cinnamon.blueberry ]";
+      example = literalExpression "[ pkgs.blueman ]";
       type = types.listOf types.package;
       description = "Which packages cinnamon should exclude from the default environment";
     };
@@ -60,23 +60,23 @@ in
 
   config = mkMerge [
     (mkIf cfg.enable {
-      services.displayManager.sessionPackages = [ pkgs.cinnamon.cinnamon-common ];
+      services.displayManager.sessionPackages = [ pkgs.cinnamon-common ];
 
       services.xserver.displayManager.lightdm.greeters.slick = {
         enable = mkDefault true;
 
         # Taken from mint-artwork.gschema.override
-        theme = mkIf (notExcluded pkgs.cinnamon.mint-themes) {
+        theme = mkIf (notExcluded pkgs.mint-themes) {
           name = mkDefault "Mint-Y-Aqua";
-          package = mkDefault pkgs.cinnamon.mint-themes;
+          package = mkDefault pkgs.mint-themes;
         };
-        iconTheme = mkIf (notExcluded pkgs.cinnamon.mint-y-icons) {
+        iconTheme = mkIf (notExcluded pkgs.mint-y-icons) {
           name = mkDefault "Mint-Y-Sand";
-          package = mkDefault pkgs.cinnamon.mint-y-icons;
+          package = mkDefault pkgs.mint-y-icons;
         };
-        cursorTheme = mkIf (notExcluded pkgs.cinnamon.mint-cursor-themes) {
+        cursorTheme = mkIf (notExcluded pkgs.mint-cursor-themes) {
           name = mkDefault "Bibata-Modern-Classic";
-          package = mkDefault pkgs.cinnamon.mint-cursor-themes;
+          package = mkDefault pkgs.mint-cursor-themes;
         };
       };
 
@@ -101,7 +101,7 @@ in
       security.polkit.enable = true;
       services.accounts-daemon.enable = true;
       services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
-      services.dbus.packages = with pkgs.cinnamon; [
+      services.dbus.packages = with pkgs; [
         cinnamon-common
         cinnamon-screensaver
         nemo-with-extensions
@@ -134,7 +134,7 @@ in
         cinnamon-screensaver = {};
       };
 
-      environment.systemPackages = with pkgs.cinnamon // pkgs; ([
+      environment.systemPackages = with pkgs; ([
         desktop-file-utils
 
         # common-files
@@ -200,7 +200,7 @@ in
         })
       ];
 
-      xdg.portal.configPackages = mkDefault [ pkgs.cinnamon.cinnamon-common ];
+      xdg.portal.configPackages = mkDefault [ pkgs.cinnamon-common ];
 
       # Override GSettings schemas
       environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-overrides}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
@@ -224,7 +224,7 @@ in
       # Default Fonts
       fonts.packages = with pkgs; [
         dejavu_fonts # Default monospace font in LMDE 6+
-        ubuntu_font_family # required for default theme
+        ubuntu-classic # required for default theme
       ];
     })
 
@@ -233,7 +233,7 @@ in
       programs.gnome-terminal.enable = mkDefault (notExcluded pkgs.gnome-terminal);
       programs.file-roller.enable = mkDefault (notExcluded pkgs.file-roller);
 
-      environment.systemPackages = with pkgs // pkgs.cinnamon; utils.removePackagesByName [
+      environment.systemPackages = with pkgs; utils.removePackagesByName [
         # cinnamon team apps
         bulky
         warpinator
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 0a341ba133d39..44c0dbc70fea8 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -92,7 +92,7 @@ in
 
     environment.etc."X11/xkb".source = xcfg.xkb.dir;
 
-    fonts.packages = [ pkgs.dejavu_fonts pkgs.ubuntu_font_family ];
+    fonts.packages = [ pkgs.dejavu_fonts ];
 
     services.udisks2.enable = true;
     services.upower.enable = config.powerManagement.enable;
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index 53d3b91bfa17c..0092a1f3693b9 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -374,6 +374,8 @@ in
         };
       };
 
+      xdg.icons.enable = true;
+
       xdg.portal.enable = true;
       xdg.portal.extraPortals = [ pkgs.plasma5Packages.xdg-desktop-portal-kde ];
       xdg.portal.configPackages = mkDefault [ pkgs.plasma5Packages.xdg-desktop-portal-kde ];
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 1a39b365db5f3..6a915a3f2f095 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -310,8 +310,11 @@ in
 
       gdm-autologin.text = ''
         auth      requisite     pam_nologin.so
-
         auth      required      pam_succeed_if.so uid >= 1000 quiet
+        ${lib.optionalString pamCfg.login.enableGnomeKeyring ''
+          auth       [success=ok default=1]      ${pkgs.gnome.gdm}/lib/security/pam_gdm.so
+          auth       optional                    ${pkgs.gnome-keyring}/lib/security/pam_gnome_keyring.so
+        ''}
         auth      required      pam_permit.so
 
         account   sufficient    pam_unix.so
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
index d20b26491aee1..4758b8d94eda2 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/slick.nix
@@ -71,8 +71,8 @@ in
       font = {
         package = mkOption {
           type = types.package;
-          default = pkgs.ubuntu_font_family;
-          defaultText = literalExpression "pkgs.ubuntu_font_family";
+          default = pkgs.ubuntu-classic;
+          defaultText = literalExpression "pkgs.ubuntu-classic";
           description = ''
             The package path that contains the font given in the name option.
           '';
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 57e83399eded6..5cec0ee0f1d51 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -648,7 +648,8 @@ in
                     || dmConf.xpra.enable
                     || dmConf.sx.enable
                     || dmConf.startx.enable
-                    || config.services.greetd.enable);
+                    || config.services.greetd.enable
+                    || config.services.displayManager.ly.enable);
       in mkIf (default) (mkDefault true);
 
     services.xserver.videoDrivers = mkIf (cfg.videoDriver != null) [ cfg.videoDriver ];
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index fc29aa3cb2f71..195ad31b1e56c 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -33,6 +33,8 @@ let
     ''
       #!${pkgs.runtimeShell}
 
+      source ${./lib/lib.sh}
+
       systemConfig='@out@'
 
       export PATH=/empty
diff --git a/nixos/modules/system/activation/lib/lib.sh b/nixos/modules/system/activation/lib/lib.sh
new file mode 100644
index 0000000000000..5ecf94e81604c
--- /dev/null
+++ b/nixos/modules/system/activation/lib/lib.sh
@@ -0,0 +1,5 @@
+# shellcheck shell=bash
+
+warn() {
+    printf "\033[1;35mwarning:\033[0m %s\n" "$*" >&2
+}
diff --git a/nixos/modules/system/activation/lib/test.nix b/nixos/modules/system/activation/lib/test.nix
new file mode 100644
index 0000000000000..39886d305195a
--- /dev/null
+++ b/nixos/modules/system/activation/lib/test.nix
@@ -0,0 +1,36 @@
+# Run:
+#   nix-build -A nixosTests.activation-lib
+{ lib, stdenv, testers }:
+let
+  inherit (lib) fileset;
+
+  runTests = stdenv.mkDerivation {
+    name = "tests-activation-lib";
+    src = fileset.toSource {
+      root = ./.;
+      fileset = fileset.unions [
+        ./lib.sh
+        ./test.sh
+      ];
+    };
+    buildPhase = ":";
+    doCheck = true;
+    postUnpack = ''
+      patchShebangs --build .
+    '';
+    checkPhase = ''
+      ./test.sh
+    '';
+    installPhase = ''
+      touch $out
+    '';
+  };
+
+  runShellcheck = testers.shellcheck {
+    src = runTests.src;
+  };
+
+in
+lib.recurseIntoAttrs {
+  inherit runTests runShellcheck;
+}
diff --git a/nixos/modules/system/activation/lib/test.sh b/nixos/modules/system/activation/lib/test.sh
new file mode 100755
index 0000000000000..9b146383ad4b0
--- /dev/null
+++ b/nixos/modules/system/activation/lib/test.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+# Run:
+#   ./test.sh
+# or:
+#   nix-build -A nixosTests.activation-lib
+
+cd "$(dirname "${BASH_SOURCE[0]}")"
+set -euo pipefail
+
+# report failure
+onerr() {
+  set +e
+  # find failed statement
+  echo "call trace:"
+  local i=0
+  while t="$(caller $i)"; do
+    line="${t%% *}"
+    file="${t##* }"
+    echo "  $file:$line" >&2
+    ((i++))
+  done
+  # red
+  printf "\033[1;31mtest failed\033[0m\n" >&2
+  exit 1
+}
+trap onerr ERR
+
+source ./lib.sh
+
+(warn hi, this works >/dev/null) 2>&1 | grep -E $'.*warning:.* hi, this works' >/dev/null
+
+# green
+printf "\033[1;32mok\033[0m\n"
diff --git a/nixos/modules/system/activation/specialisation.nix b/nixos/modules/system/activation/specialisation.nix
index fdab287802fa5..fc348ad94c03a 100644
--- a/nixos/modules/system/activation/specialisation.nix
+++ b/nixos/modules/system/activation/specialisation.nix
@@ -1,10 +1,14 @@
-{ config, lib, pkgs, extendModules, noUserModules, ... }:
+{ config, lib, extendModules, noUserModules, ... }:
 
 let
   inherit (lib)
+    attrNames
     concatStringsSep
+    filter
+    length
     mapAttrs
     mapAttrsToList
+    match
     mkOption
     types
     ;
@@ -73,6 +77,19 @@ in
   };
 
   config = {
+    assertions = [(
+      let
+        invalidNames = filter (name: match "[[:alnum:]_]+" name == null) (attrNames config.specialisation);
+      in
+      {
+        assertion = length invalidNames == 0;
+        message = ''
+          Specialisation names can only contain alphanumeric characters and underscores
+          Invalid specialisation names: ${concatStringsSep ", " invalidNames}
+        '';
+      }
+    )];
+
     system.systemBuilderCommands = ''
       mkdir $out/specialisation
       ${concatStringsSep "\n"
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index cabc1dcc2d65a..4beca4f0a42a9 100755
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -1,5 +1,10 @@
 #! @perl@/bin/perl
 
+# NOTE: This script has an alternative implementation at
+# <nixpkgs/pkgs/by-name/sw/switch-to-configuration-ng>. Any behavioral
+# modifications to this script should also be made to that implementation.
+
+
 # Issue #166838 uncovered a situation in which a configuration not suitable
 # for the target architecture caused a cryptic error message instead of
 # a clean failure. Due to this mismatch, the perl interpreter in the shebang
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 1d702442f7f66..a0ef92c0505f4 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -141,6 +141,10 @@ let
       magicOrExtension = ''\x00asm'';
       mask = ''\xff\xff\xff\xff'';
     };
+    s390x-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x16'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
     x86_64-windows.magicOrExtension = "MZ";
     i686-windows.magicOrExtension = "MZ";
   };
diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
index 630c6e1870e6e..397326899d8d6 100644
--- a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -42,7 +42,7 @@ in
         default = false;
         type = types.bool;
         description = ''
-          Whether copy the necessary boot files into /boot, so
+          Whether to copy the necessary boot files into /boot, so
           /nix/store is not needed by the boot loader.
         '';
       };
diff --git a/nixos/modules/system/boot/loader/systemd-boot/boot-counting.md b/nixos/modules/system/boot/loader/systemd-boot/boot-counting.md
new file mode 100644
index 0000000000000..743584b525915
--- /dev/null
+++ b/nixos/modules/system/boot/loader/systemd-boot/boot-counting.md
@@ -0,0 +1,38 @@
+# Automatic boot assessment with systemd-boot {#sec-automatic-boot-assessment}
+
+## Overview {#sec-automatic-boot-assessment-overview}
+
+Automatic boot assessment (or boot-counting) is a feature of `systemd-boot` that allows for automatically detecting invalid boot entries.
+When the feature is active, each boot entry has an associated counter with a user defined number of trials. Whenever `systemd-boot` boots an entry, its counter is decreased by one, ultimately being marked as *bad* if the counter ever reaches zero. However, if an entry is successfully booted, systemd will permanently mark it as *good* and remove the counter altogether. Whenever an entry is marked as *bad*, it is sorted last in the `systemd-boot` menu.
+A complete explanation of how that feature works can be found [here](https://systemd.io/AUTOMATIC_BOOT_ASSESSMENT/).
+
+## Enabling the feature {#sec-automatic-boot-assessment-enable}
+
+The feature can be enabled by toogling the [boot.loader.systemd-boot.bootCounting](#opt-boot.loader.systemd-boot.bootCounting.enable) option.
+
+## The boot-complete.target unit {#sec-automatic-boot-assessment-boot-complete-target}
+
+A *successful boot* for an entry is defined in terms of the `boot-complete.target` synchronisation point. It is up to the user to schedule all necessary units for the machine to be considered successfully booted before that synchronisation point.
+For example, if you are running `docker` on a machine and you want to be sure that a *good* entry is an entry where docker is started successfully.
+A configuration for that NixOS machine could look like that:
+
+```
+boot.loader.systemd-boot.bootCounting.enable = true;
+services.docker.enable = true;
+
+systemd.services.docker = {
+  before = [ "boot-complete.target" ];
+  wantedBy = [ "boot-complete.target" ];
+  unitConfig.FailureAction = "reboot";
+};
+```
+
+The systemd service type must be of type `notify` or `oneshot` for systemd to dectect the startup error properly.
+
+## Interaction with specialisations {#sec-automatic-boot-assessment-specialisations}
+
+When the boot-counting feature is enabled, `systemd-boot` will still try the boot entries in the same order as they are displayed in the boot menu. This means that the specialisations of a given generation will be tried directly after that generation, but that behavior is customizable with the [boot.loader.systemd-boot.sortKey](#opt-boot.loader.systemd-boot.sortKey) option.
+
+## Limitations {#sec-automatic-boot-assessment-limitations}
+
+This feature has to be used wisely to not risk any data integrity issues. Rollbacking into past generations can sometimes be dangerous, for example if some of the services may have undefined behaviors in the presence of unrecognized data migrations from future versions of themselves.
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index 694d34d1c059a..c4324a8eae5bc 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -12,8 +12,9 @@ import subprocess
 import sys
 import warnings
 import json
-from typing import NamedTuple, Dict, List
+from typing import NamedTuple, Any, Type
 from dataclasses import dataclass
+from pathlib import Path
 
 # These values will be replaced with actual values during the package build
 EFI_SYS_MOUNT_POINT = "@efiSysMountPoint@"
@@ -21,34 +22,145 @@ BOOT_MOUNT_POINT = "@bootMountPoint@"
 LOADER_CONF = f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf"  # Always stored on the ESP
 NIXOS_DIR = "@nixosDir@"
 TIMEOUT = "@timeout@"
-EDITOR = "@editor@" == "1"
+EDITOR = "@editor@" == "1" # noqa: PLR0133
 CONSOLE_MODE = "@consoleMode@"
 BOOTSPEC_TOOLS = "@bootspecTools@"
 DISTRO_NAME = "@distroName@"
 NIX = "@nix@"
 SYSTEMD = "@systemd@"
 CONFIGURATION_LIMIT = int("@configurationLimit@")
+REBOOT_FOR_BITLOCKER = bool("@rebootForBitlocker@")
 CAN_TOUCH_EFI_VARIABLES = "@canTouchEfiVariables@"
 GRACEFUL = "@graceful@"
 COPY_EXTRA_FILES = "@copyExtraFiles@"
 CHECK_MOUNTPOINTS = "@checkMountpoints@"
+BOOT_COUNTING_TRIES = "@bootCountingTries@"
+BOOT_COUNTING = "@bootCounting@" == "True"
 
 @dataclass
 class BootSpec:
     init: str
     initrd: str
     kernel: str
-    kernelParams: List[str]
+    kernelParams: list[str]  # noqa: N815
     label: str
     system: str
     toplevel: str
-    specialisations: Dict[str, "BootSpec"]
-    sortKey: str
-    initrdSecrets: str | None = None
+    specialisations: dict[str, "BootSpec"]
+    sortKey: str  # noqa: N815
+    devicetree: str | None = None  # noqa: N815
+    initrdSecrets: str | None = None  # noqa: N815
 
+@dataclass
+class Entry:
+    profile: str | None
+    generation_number: int
+    specialisation: str | None
+
+    @classmethod
+    def from_path(cls: Type["Entry"], path: Path) -> "Entry":
+        filename = path.name
+        # Matching nixos-$profile-generation-*.conf
+        rex_profile = re.compile(r"^nixos-(.*)-generation-.*\.conf$")
+        # Matching nixos*-generation-$number*.conf
+        rex_generation = re.compile(r"^nixos.*-generation-([0-9]+).*\.conf$")
+        # Matching nixos*-generation-$number-specialisation-$specialisation_name*.conf
+        rex_specialisation = re.compile(r"^nixos.*-generation-([0-9]+)-specialisation-([a-zA-Z0-9_]+).*\.conf$")
+        profile = rex_profile.sub(r"\1", filename) if rex_profile.match(filename) else None
+        specialisation = rex_specialisation.sub(r"\2", filename) if rex_specialisation.match(filename) else None
+        try:
+            generation_number = int(rex_generation.sub(r"\1", filename))
+        except ValueError:
+            raise
+        return cls(profile, generation_number, specialisation)
+
+@dataclass
+class DiskEntry:
+    entry: Entry
+    default: bool
+    counters: str | None
+    title: str | None
+    description: str | None
+    kernel: str
+    initrd: str
+    kernel_params: str | None
+    machine_id: str | None
+    sort_key: str
+    devicetree: str | None
+
+    @classmethod
+    def from_path(cls: Type["DiskEntry"], path: Path) -> "DiskEntry":
+        entry = Entry.from_path(path)
+        data = path.read_text().splitlines()
+        if '' in data:
+            data.remove('')
+        entry_map = dict(lines.split(' ', 1) for lines in data)
+        assert "linux" in entry_map
+        assert "initrd" in entry_map
+        filename = path.name
+        # Matching nixos*-generation-*$counters.conf
+        rex_counters = re.compile(r"^nixos.*-generation-.*(\+\d(-\d)?)\.conf$")
+        counters = rex_counters.sub(r"\1", filename) if rex_counters.match(filename) else None
+        disk_entry = cls(
+            entry=entry,
+            default=(entry_map.get("sort-key") == "default"),
+            counters=counters,
+            title=entry_map.get("title"),
+            description=entry_map.get("version"),
+            kernel=entry_map["linux"],
+            initrd=entry_map["initrd"],
+            kernel_params=entry_map.get("options"),
+            machine_id=entry_map.get("machine-id"),
+            sort_key=entry_map.get("sort_key", "nixos"),
+            devicetree=entry_map.get("devicetree"),
+        )
+        return disk_entry
+
+    def write(self, sorted_first: str) -> None:
+        # Compute a sort-key sorted before sorted_first
+        # This will compute something like: nixos -> nixor-default to make sure we come before other nixos entries,
+        # while allowing users users can pre-pend their own entries before.
+        default_sort_key = sorted_first[:-1] + chr(ord(sorted_first[-1])-1) + "-default"
+        tmp_path = self.path.with_suffix(".tmp")
+        with tmp_path.open('w') as f:
+            # We use "sort-key" to sort the default generation first.
+            # The "default" string is sorted before "non-default" (alphabetically)
+            boot_entry = [
+                f"title {self.title}" if self.title is not None else None,
+                f"version {self.description}" if self.description is not None else None,
+                f"linux {self.kernel}",
+                f"initrd  {self.initrd}",
+                f"options {self.kernel_params}" if self.kernel_params is not None else None,
+                f"machine-id {self.machine_id}" if self.machine_id is not None else None,
+                f"sort-key {default_sort_key if self.default else self.sort_key}",
+                f"devicetree {self.devicetree}" if self.devicetree is not None else None,
+            ]
+
+            f.write("\n".join(filter(None, boot_entry)))
+            f.flush()
+            os.fsync(f.fileno())
+        tmp_path.rename(self.path)
+
+
+    @property
+    def path(self) -> Path:
+        pieces = [
+            "nixos",
+            self.entry.profile or None,
+            "generation",
+            str(self.entry.generation_number),
+            f"specialisation-{self.entry.specialisation}" if self.entry.specialisation else None,
+        ]
+        prefix = "-".join(p for p in pieces if p)
+        return Path(f"{BOOT_MOUNT_POINT}/loader/entries/{prefix}{self.counters if self.counters else ''}.conf")
 
 libc = ctypes.CDLL("libc.so.6")
 
+FILE = None | int
+
+def run(cmd: list[str], stdout: FILE = None) -> subprocess.CompletedProcess[str]:
+    return subprocess.run(cmd, check=True, text=True, stdout=stdout)
+
 class SystemIdentifier(NamedTuple):
     profile: str | None
     generation: int
@@ -73,37 +185,35 @@ def system_dir(profile: str | None, generation: int, specialisation: str | None)
     else:
         return d
 
-BOOT_ENTRY = """title {title}
-sort-key {sort_key}
-version Generation {generation} {description}
-linux {kernel}
-initrd {initrd}
-options {kernel_params}
-"""
-
-def generation_conf_filename(profile: str | None, generation: int, specialisation: str | None) -> str:
-    pieces = [
-        "nixos",
-        profile or None,
-        "generation",
-        str(generation),
-        f"specialisation-{specialisation}" if specialisation else None,
-    ]
-    return "-".join(p for p in pieces if p) + ".conf"
-
-
-def write_loader_conf(profile: str | None, generation: int, specialisation: str | None) -> None:
-    with open(f"{LOADER_CONF}.tmp", 'w') as f:
-        if TIMEOUT != "":
-            f.write(f"timeout {TIMEOUT}\n")
-        f.write("default %s\n" % generation_conf_filename(profile, generation, specialisation))
+def write_loader_conf(profile: str | None) -> None:
+    with open(f"{EFI_SYS_MOUNT_POINT}/loader/loader.conf.tmp", 'w') as f:
+        f.write(f"timeout {TIMEOUT}\n")
+        if profile:
+            f.write("default nixos-%s-generation-*\n" % profile)
+        else:
+            f.write("default nixos-generation-*\n")
         if not EDITOR:
             f.write("editor 0\n")
+        if REBOOT_FOR_BITLOCKER:
+            f.write("reboot-for-bitlocker yes\n");
         f.write(f"console-mode {CONSOLE_MODE}\n")
         f.flush()
         os.fsync(f.fileno())
     os.rename(f"{LOADER_CONF}.tmp", LOADER_CONF)
 
+def scan_entries() -> list[DiskEntry]:
+    """
+    Scan all entries in $ESP/loader/entries/*
+    Does not support Type 2 entries as we do not support them for now.
+    Returns a generator of Entry.
+    """
+    entries = []
+    for path in Path(f"{EFI_SYS_MOUNT_POINT}/loader/entries/").glob("nixos*-generation-[1-9]*.conf"):
+        try:
+            entries.append(DiskEntry.from_path(path))
+        except ValueError:
+            continue
+    return entries
 
 def get_bootspec(profile: str | None, generation: int) -> BootSpec:
     system_directory = system_dir(profile, generation, None)
@@ -112,25 +222,30 @@ def get_bootspec(profile: str | None, generation: int) -> BootSpec:
         boot_json_f = open(boot_json_path, 'r')
         bootspec_json = json.load(boot_json_f)
     else:
-        boot_json_str = subprocess.check_output([
-        f"{BOOTSPEC_TOOLS}/bin/synthesize",
-        "--version",
-        "1",
-        system_directory,
-        "/dev/stdout"],
-        universal_newlines=True)
+        boot_json_str = run(
+            [
+                f"{BOOTSPEC_TOOLS}/bin/synthesize",
+                "--version",
+                "1",
+                system_directory,
+                "/dev/stdout",
+            ],
+            stdout=subprocess.PIPE,
+        ).stdout
         bootspec_json = json.loads(boot_json_str)
     return bootspec_from_json(bootspec_json)
 
-def bootspec_from_json(bootspec_json: Dict) -> BootSpec:
+def bootspec_from_json(bootspec_json: dict[str, Any]) -> BootSpec:
     specialisations = bootspec_json['org.nixos.specialisation.v1']
     specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()}
     systemdBootExtension = bootspec_json.get('org.nixos.systemd-boot', {})
     sortKey = systemdBootExtension.get('sortKey', 'nixos')
+    devicetree = systemdBootExtension.get('devicetree')
     return BootSpec(
         **bootspec_json['org.nixos.bootspec.v1'],
         specialisations=specialisations,
-        sortKey=sortKey
+        sortKey=sortKey,
+        devicetree=devicetree,
     )
 
 
@@ -143,12 +258,19 @@ def copy_from_file(file: str, dry_run: bool = False) -> str:
         copy_if_not_exists(store_file_path, f"{BOOT_MOUNT_POINT}{efi_file_path}")
     return efi_file_path
 
-def write_entry(profile: str | None, generation: int, specialisation: str | None,
-                machine_id: str, bootspec: BootSpec, current: bool) -> None:
+def write_entry(profile: str | None,
+                generation: int,
+                specialisation: str | None,
+                machine_id: str,
+                bootspec: BootSpec,
+                entries: list[DiskEntry],
+                sorted_first: str,
+                current: bool) -> None:
     if specialisation:
         bootspec = bootspec.specialisations[specialisation]
     kernel = copy_from_file(bootspec.kernel)
     initrd = copy_from_file(bootspec.initrd)
+    devicetree = copy_from_file(bootspec.devicetree) if bootspec.devicetree is not None else None
 
     title = "{name}{profile}{specialisation}".format(
         name=DISTRO_NAME,
@@ -157,7 +279,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
 
     try:
         if bootspec.initrdSecrets is not None:
-            subprocess.check_call([bootspec.initrdSecrets, f"{BOOT_MOUNT_POINT}%s" % (initrd)])
+            run([bootspec.initrdSecrets, f"{BOOT_MOUNT_POINT}%s" % (initrd)])
     except subprocess.CalledProcessError:
         if current:
             print("failed to create initrd secrets!", file=sys.stderr)
@@ -167,38 +289,46 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
                   f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr)
             print("note: this is normal after having removed "
                   "or renamed a file in `boot.initrd.secrets`", file=sys.stderr)
-    entry_file = f"{BOOT_MOUNT_POINT}/loader/entries/%s" % (
-        generation_conf_filename(profile, generation, specialisation))
-    tmp_path = "%s.tmp" % (entry_file)
     kernel_params = "init=%s " % bootspec.init
-
     kernel_params = kernel_params + " ".join(bootspec.kernelParams)
     build_time = int(os.path.getctime(system_dir(profile, generation, specialisation)))
     build_date = datetime.datetime.fromtimestamp(build_time).strftime('%F')
-
-    with open(tmp_path, 'w') as f:
-        f.write(BOOT_ENTRY.format(title=title,
-                    sort_key=bootspec.sortKey,
-                    generation=generation,
-                    kernel=kernel,
-                    initrd=initrd,
-                    kernel_params=kernel_params,
-                    description=f"{bootspec.label}, built on {build_date}"))
-        if machine_id is not None:
-            f.write("machine-id %s\n" % machine_id)
-        f.flush()
-        os.fsync(f.fileno())
-    os.rename(tmp_path, entry_file)
-
+    counters = f"+{BOOT_COUNTING_TRIES}" if BOOT_COUNTING else ""
+    entry = Entry(profile, generation, specialisation)
+    # We check if the entry we are writing is already on disk
+    # and we update its "default entry" status
+    for entry_on_disk in entries:
+        if entry == entry_on_disk.entry:
+            entry_on_disk.default = current
+            entry_on_disk.write(sorted_first)
+            return
+
+    DiskEntry(
+        entry=entry,
+        title=title,
+        kernel=kernel,
+        initrd=initrd,
+        counters=counters,
+        kernel_params=kernel_params,
+        machine_id=machine_id,
+        description=f"Generation {generation} {bootspec.label}, built on {build_date}",
+        sort_key=bootspec.sortKey,
+        devicetree=devicetree,
+        default=current
+    ).write(sorted_first)
 
 def get_generations(profile: str | None = None) -> list[SystemIdentifier]:
-    gen_list = subprocess.check_output([
-        f"{NIX}/bin/nix-env",
-        "--list-generations",
-        "-p",
-        "/nix/var/nix/profiles/%s" % ("system-profiles/" + profile if profile else "system")],
-        universal_newlines=True)
-    gen_lines = gen_list.split('\n')
+    gen_list = run(
+        [
+            f"{NIX}/bin/nix-env",
+            "--list-generations",
+            "-p",
+            "/nix/var/nix/profiles/%s"
+            % ("system-profiles/" + profile if profile else "system"),
+        ],
+        stdout=subprocess.PIPE,
+    ).stdout
+    gen_lines = gen_list.split("\n")
     gen_lines.pop()
 
     configurationLimit = CONFIGURATION_LIMIT
@@ -213,30 +343,19 @@ def get_generations(profile: str | None = None) -> list[SystemIdentifier]:
     return configurations[-configurationLimit:]
 
 
-def remove_old_entries(gens: list[SystemIdentifier]) -> None:
-    rex_profile = re.compile(r"^" + re.escape(BOOT_MOUNT_POINT) + "/loader/entries/nixos-(.*)-generation-.*\.conf$")
-    rex_generation = re.compile(r"^" + re.escape(BOOT_MOUNT_POINT) + "/loader/entries/nixos.*-generation-([0-9]+)(-specialisation-.*)?\.conf$")
+def remove_old_entries(gens: list[SystemIdentifier], disk_entries: list[DiskEntry]) -> None:
     known_paths = []
     for gen in gens:
         bootspec = get_bootspec(gen.profile, gen.generation)
         known_paths.append(copy_from_file(bootspec.kernel, True))
         known_paths.append(copy_from_file(bootspec.initrd, True))
-    for path in glob.iglob(f"{BOOT_MOUNT_POINT}/loader/entries/nixos*-generation-[1-9]*.conf"):
-        if rex_profile.match(path):
-            prof = rex_profile.sub(r"\1", path)
-        else:
-            prof = None
-        try:
-            gen_number = int(rex_generation.sub(r"\1", path))
-        except ValueError:
-            continue
-        if not (prof, gen_number, None) in gens:
-            os.unlink(path)
-    for path in glob.iglob(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/*"):
-        if not path in known_paths and not os.path.isdir(path):
+    for disk_entry in disk_entries:
+        if (disk_entry.entry.profile, disk_entry.entry.generation_number, None) not in gens:
+            os.unlink(disk_entry.path)
+    for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/efi/nixos/*"):
+        if path not in known_paths and not os.path.isdir(path):
             os.unlink(path)
 
-
 def cleanup_esp() -> None:
     for path in glob.iglob(f"{EFI_SYS_MOUNT_POINT}/loader/entries/nixos*"):
         os.unlink(path)
@@ -255,7 +374,7 @@ def get_profiles() -> list[str]:
 def install_bootloader(args: argparse.Namespace) -> None:
     try:
         with open("/etc/machine-id") as machine_file:
-            machine_id = machine_file.readlines()[0]
+            machine_id = machine_file.readlines()[0].strip()
     except IOError as e:
         if e.errno != errno.ENOENT:
             raise
@@ -263,9 +382,7 @@ def install_bootloader(args: argparse.Namespace) -> None:
         # be there on newly installed systems, so let's generate one so that
         # bootctl can find it and we can also pass it to write_entry() later.
         cmd = [f"{SYSTEMD}/bin/systemd-machine-id-setup", "--print"]
-        machine_id = subprocess.run(
-          cmd, text=True, check=True, stdout=subprocess.PIPE
-        ).stdout.rstrip()
+        machine_id = run(cmd, stdout=subprocess.PIPE).stdout.rstrip()
 
     if os.getenv("NIXOS_INSTALL_GRUB") == "1":
         warnings.warn("NIXOS_INSTALL_GRUB env var deprecated, use NIXOS_INSTALL_BOOTLOADER", DeprecationWarning)
@@ -288,14 +405,32 @@ def install_bootloader(args: argparse.Namespace) -> None:
         if os.path.exists(LOADER_CONF):
             os.unlink(LOADER_CONF)
 
-        subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["install"])
+        run(
+            [f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"]
+            + bootctl_flags
+            + ["install"]
+        )
     else:
         # Update bootloader to latest if needed
-        available_out = subprocess.check_output([f"{SYSTEMD}/bin/bootctl", "--version"], universal_newlines=True).split()[2]
-        installed_out = subprocess.check_output([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}", "status"], universal_newlines=True)
+        available_out = run(
+            [f"{SYSTEMD}/bin/bootctl", "--version"], stdout=subprocess.PIPE
+        ).stdout.split()[2]
+        installed_out = run(
+            [f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}", "status"],
+            stdout=subprocess.PIPE,
+        ).stdout
 
         # See status_binaries() in systemd bootctl.c for code which generates this
-        installed_match = re.search(r"^\W+File:.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
+        # Matches
+        # Available Boot Loaders on ESP:
+        #  ESP: /boot (/dev/disk/by-partuuid/9b39b4c4-c48b-4ebf-bfea-a56b2395b7e0)
+        # File: └─/EFI/systemd/systemd-bootx64.efi (systemd-boot 255.2)
+        # But also:
+        # Available Boot Loaders on ESP:
+        #  ESP: /boot (/dev/disk/by-partuuid/9b39b4c4-c48b-4ebf-bfea-a56b2395b7e0)
+        # File: ├─/EFI/systemd/HashTool.efi
+        #       └─/EFI/systemd/systemd-bootx64.efi (systemd-boot 255.2)
+        installed_match = re.search(r"^\W+.*/EFI/(?:BOOT|systemd)/.*\.efi \(systemd-boot ([\d.]+[^)]*)\)$",
                       installed_out, re.IGNORECASE | re.MULTILINE)
 
         available_match = re.search(r"^\((.*)\)$", available_out)
@@ -311,7 +446,11 @@ def install_bootloader(args: argparse.Namespace) -> None:
 
         if installed_version < available_version:
             print("updating systemd-boot from %s to %s" % (installed_version, available_version))
-            subprocess.check_call([f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"] + bootctl_flags + ["update"])
+            run(
+                [f"{SYSTEMD}/bin/bootctl", f"--esp-path={EFI_SYS_MOUNT_POINT}"]
+                + bootctl_flags
+                + ["update"]
+            )
 
     os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}", exist_ok=True)
     os.makedirs(f"{BOOT_MOUNT_POINT}/loader/entries", exist_ok=True)
@@ -319,18 +458,32 @@ def install_bootloader(args: argparse.Namespace) -> None:
     gens = get_generations()
     for profile in get_profiles():
         gens += get_generations(profile)
-
-    remove_old_entries(gens)
+    entries = scan_entries()
+    remove_old_entries(gens, entries)
+    # Compute the sort-key that will be sorted first.
+    sorted_first = ""
+    for gen in gens:
+        try:
+            bootspec = get_bootspec(gen.profile, gen.generation)
+            if bootspec.sortKey < sorted_first or sorted_first == "":
+                sorted_first = bootspec.sortKey
+        except OSError as e:
+            # See https://github.com/NixOS/nixpkgs/issues/114552
+            if e.errno == errno.EINVAL:
+                profile = f"profile '{gen.profile}'" if gen.profile else "default profile"
+                print("ignoring {} in the list of boot entries because of the following error:\n{}".format(profile, e), file=sys.stderr)
+            else:
+                raise e
 
     for gen in gens:
         try:
             bootspec = get_bootspec(gen.profile, gen.generation)
             is_default = os.path.dirname(bootspec.init) == args.default_config
-            write_entry(*gen, machine_id, bootspec, current=is_default)
+            write_entry(*gen, machine_id, bootspec, entries, sorted_first, current=is_default)
             for specialisation in bootspec.specialisations.keys():
-                write_entry(gen.profile, gen.generation, specialisation, machine_id, bootspec, current=is_default)
+                write_entry(gen.profile, gen.generation, specialisation, machine_id, bootspec, entries, sorted_first, current=(is_default and bootspec.specialisations[specialisation].sortKey == bootspec.sortKey))
             if is_default:
-                write_loader_conf(*gen)
+                write_loader_conf(gen.profile)
         except OSError as e:
             # See https://github.com/NixOS/nixpkgs/issues/114552
             if e.errno == errno.EINVAL:
@@ -362,7 +515,7 @@ def install_bootloader(args: argparse.Namespace) -> None:
 
     os.makedirs(f"{BOOT_MOUNT_POINT}/{NIXOS_DIR}/.extra-files", exist_ok=True)
 
-    subprocess.check_call(COPY_EXTRA_FILES)
+    run([COPY_EXTRA_FILES])
 
 
 def main() -> None:
@@ -370,7 +523,7 @@ def main() -> None:
     parser.add_argument('default_config', metavar='DEFAULT-CONFIG', help=f"The default {DISTRO_NAME} config to boot")
     args = parser.parse_args()
 
-    subprocess.check_call(CHECK_MOUNTPOINTS)
+    run([CHECK_MOUNTPOINTS])
 
     try:
         install_bootloader(args)
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 e73048dc2ecbe..bd4dbe96ff3a7 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -22,6 +22,8 @@ let
   '';
 
   systemdBootBuilder = pkgs.substituteAll rec {
+    name = "systemd-boot";
+
     src = checkedSource;
 
     isExecutable = true;
@@ -34,11 +36,11 @@ let
 
     nix = config.nix.package.out;
 
-    timeout = optionalString (config.boot.loader.timeout != null) config.boot.loader.timeout;
+    timeout = if config.boot.loader.timeout == null then "menu-force" else config.boot.loader.timeout;
 
     configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit;
 
-    inherit (cfg) consoleMode graceful editor;
+    inherit (cfg) consoleMode graceful editor rebootForBitlocker;
 
     inherit (efi) efiSysMountPoint canTouchEfiVariables;
 
@@ -78,6 +80,8 @@ let
         ${pkgs.coreutils}/bin/install -D $empty_file "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n}
       '') cfg.extraEntries)}
     '';
+    bootCountingTries = cfg.bootCounting.tries;
+    bootCounting = if cfg.bootCounting.enable then "True" else "False";
   };
 
   finalSystemdBootBuilder = pkgs.writeScript "install-systemd-boot.sh" ''
@@ -87,7 +91,10 @@ let
   '';
 in {
 
-  meta.maintainers = with lib.maintainers; [ julienmalka ];
+  meta = {
+    maintainers = with lib.maintainers; [ julienmalka ];
+    doc = ./boot-counting.md;
+  };
 
   imports =
     [ (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "enable" ] [ "boot" "loader" "systemd-boot" "enable" ])
@@ -184,6 +191,15 @@ in {
       '';
     };
 
+    installDeviceTree = mkOption {
+      default = with config.hardware.deviceTree; enable && name != null;
+      defaultText = ''with config.hardware.deviceTree; enable && name != null'';
+      description = ''
+        Install the devicetree blob specified by `config.hardware.deviceTree.name`
+        to the ESP and instruct systemd-boot to pass this DTB to linux.
+      '';
+    };
+
     extraInstallCommands = mkOption {
       default = "";
       example = ''
@@ -317,6 +333,31 @@ in {
       '';
     };
 
+    bootCounting = {
+      enable = mkEnableOption "automatic boot assessment";
+      tries = mkOption {
+        default = 3;
+        type = types.int;
+        description = "number of tries each entry should start with";
+      };
+    };
+
+    rebootForBitlocker = mkOption {
+      default = false;
+
+      type = types.bool;
+
+      description = ''
+        Enable *EXPERIMENTAL* BitLocker support.
+
+        Try to detect BitLocker encrypted drives along with an active
+        TPM. If both are found and Windows Boot Manager is selected in
+        the boot menu, set the "BootNext" EFI variable and restart the
+        system. The firmware will then start Windows Boot Manager
+        directly, leaving the TPM PCRs in expected states so that
+        Windows can unseal the encryption key.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -337,6 +378,10 @@ in {
         assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub;
         message = "This kernel does not support the EFI boot stub";
       }
+      {
+        assertion = cfg.installDeviceTree -> config.hardware.deviceTree.enable -> config.hardware.deviceTree.name != null;
+        message = "Cannot install devicetree without 'config.hardware.deviceTree.enable' enabled and 'config.hardware.deviceTree.name' set";
+      }
     ] ++ concatMap (filename: [
       {
         assertion = !(hasInfix "/" filename);
@@ -394,6 +439,7 @@ in {
 
     boot.bootspec.extensions."org.nixos.systemd-boot" = {
       inherit (config.boot.loader.systemd-boot) sortKey;
+      devicetree = lib.mkIf cfg.installDeviceTree "${config.hardware.deviceTree.package}/${config.hardware.deviceTree.name}";
     };
 
     system = {
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 761bbe6e03d4a..43fa93aadf1c2 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -18,12 +18,16 @@ let
           "ManageForeignRoutes"
           "RouteTable"
           "IPv6PrivacyExtensions"
+          "IPv4Forwarding"
+          "IPv6Forwarding"
         ])
         (assertValueOneOf "SpeedMeter" boolValues)
         (assertInt "SpeedMeterIntervalSec")
         (assertValueOneOf "ManageForeignRoutingPolicyRules" boolValues)
         (assertValueOneOf "ManageForeignRoutes" boolValues)
         (assertValueOneOf "IPv6PrivacyExtensions" (boolValues ++ ["prefer-public" "kernel"]))
+        (assertValueOneOf "IPv4Forwarding" boolValues)
+        (assertValueOneOf "IPv6Forwarding" boolValues)
       ];
 
       sectionDHCPv4 = checkUnitConfig "DHCPv4" [
@@ -119,10 +123,12 @@ let
           "VNetHeader"
           "User"
           "Group"
+          "KeepCarrier"
         ])
         (assertValueOneOf "MultiQueue" boolValues)
         (assertValueOneOf "PacketInfo" boolValues)
         (assertValueOneOf "VNetHeader" boolValues)
+        (assertValueOneOf "KeepCarrier" boolValues)
       ];
 
       # See https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html#%5BIPVTAP%5D%20Section%20Options
@@ -632,6 +638,7 @@ let
           "LinkLocalAddressing"
           "IPv6LinkLocalAddressGenerationMode"
           "IPv6StableSecretAddress"
+          "IPv4LLStartAddress"
           "IPv4LLRoute"
           "DefaultRouteOnDevice"
           "LLMNR"
@@ -649,17 +656,23 @@ let
           "DNSDefaultRoute"
           "NTP"
           "IPForward"
+          "IPv4Forwarding"
+          "IPv6Forwarding"
           "IPMasquerade"
           "IPv6PrivacyExtensions"
           "IPv6AcceptRA"
           "IPv6DuplicateAddressDetection"
           "IPv6HopLimit"
+          "IPv4ReversePathFilter"
+          "IPv4AcceptLocal"
+          "IPv4RouteLocalnet"
           "IPv4ProxyARP"
           "IPv6ProxyNDP"
           "IPv6ProxyNDPAddress"
           "IPv6SendRA"
           "DHCPPrefixDelegation"
           "IPv6MTUBytes"
+          "KeepMaster"
           "Bridge"
           "Bond"
           "VRF"
@@ -693,7 +706,9 @@ let
         (assertValueOneOf "LLDP" (boolValues ++ ["routers-only"]))
         (assertValueOneOf "EmitLLDP" (boolValues ++ ["nearest-bridge" "non-tpmr-bridge" "customer-bridge"]))
         (assertValueOneOf "DNSDefaultRoute" boolValues)
-        (assertValueOneOf "IPForward" (boolValues ++ ["ipv4" "ipv6"]))
+        (assertRemoved "IPForward" "IPv4Forwarding and IPv6Forwarding in systemd.network(5) and networkd.conf(5)")
+        (assertValueOneOf "IPv4Forwarding" boolValues)
+        (assertValueOneOf "IPv6Forwarding" boolValues)
         (assertValueOneOf "IPMasquerade" (boolValues ++ ["ipv4" "ipv6" "both"]))
         (assertValueOneOf "IPv6PrivacyExtensions" (boolValues ++ ["prefer-public" "kernel"]))
         (assertValueOneOf "IPv6AcceptRA" boolValues)
@@ -701,11 +716,15 @@ let
         (assertMinimum "IPv6DuplicateAddressDetection" 0)
         (assertInt "IPv6HopLimit")
         (assertMinimum "IPv6HopLimit" 0)
+        (assertValueOneOf "IPv4ReversePathFilter" ["no" "strict" "loose"])
+        (assertValueOneOf "IPv4AcceptLocal" boolValues)
+        (assertValueOneOf "IPv4RouteLocalnet" boolValues)
         (assertValueOneOf "IPv4ProxyARP" boolValues)
         (assertValueOneOf "IPv6ProxyNDP" boolValues)
         (assertValueOneOf "IPv6SendRA" boolValues)
         (assertValueOneOf "DHCPPrefixDelegation" boolValues)
         (assertByteFormat "IPv6MTUBytes")
+        (assertValueOneOf "KeepMaster" boolValues)
         (assertValueOneOf "ActiveSlave" boolValues)
         (assertValueOneOf "PrimarySlave" boolValues)
         (assertValueOneOf "ConfigureWithoutCarrier" boolValues)
@@ -759,8 +778,7 @@ let
         ])
         (assertInt "TypeOfService")
         (assertRange "TypeOfService" 0 255)
-        (assertInt "FirewallMark")
-        (assertRange "FirewallMark" 1 4294967295)
+        (assertRangeWithOptionalMask "FirewallMark" 1 4294967295)
         (assertInt "Priority")
         (assertPortOrPortRange "SourcePort")
         (assertPortOrPortRange "DestinationPort")
@@ -999,6 +1017,7 @@ let
           "BootServerAddress"
           "BootServerName"
           "BootFilename"
+          "IPv6OnlyPreferredSec"
         ])
         (assertInt "PoolOffset")
         (assertMinimum "PoolOffset" 0)
@@ -2824,6 +2843,7 @@ let
         "systemd-networkd-wait-online.service"
         "systemd-networkd.service"
         "systemd-networkd.socket"
+        "systemd-networkd-persistent-storage.service"
       ];
 
       environment.etc."systemd/networkd.conf" = renderConfig cfg.config;
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index 4fed6335f7421..68c3286b22a06 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -219,7 +219,7 @@ in
         # Fonts
         "/etc/plymouth/fonts".source = pkgs.runCommand "plymouth-initrd-fonts" {} ''
           mkdir -p $out
-          cp ${cfg.font} $out
+          cp ${escapeShellArg cfg.font} $out
         '';
         "/etc/fonts/fonts.conf".text = ''
           <?xml version="1.0"?>
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index ae05bc5ae88c4..082380216d2a7 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -131,6 +131,7 @@ let
 
       # Copy udev.
       copy_bin_and_libs ${udev}/bin/udevadm
+      cp ${lib.getLib udev.kmod}/lib/libkmod.so* $out/lib
       copy_bin_and_libs ${udev}/lib/systemd/systemd-sysctl
       for BIN in ${udev}/lib/udev/*_id; do
         copy_bin_and_libs $BIN
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 76a6751b05708..85e9b0a68b46a 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -37,6 +37,8 @@ let
       "cryptsetup.target"
       "cryptsetup-pre.target"
       "remote-cryptsetup.target"
+    ] ++ optionals cfg.package.withTpm2Tss [
+      "tpm2.target"
     ] ++ [
       "sigpwr.target"
       "timers.target"
@@ -105,6 +107,10 @@ let
       "systemd-rfkill.service"
       "systemd-rfkill.socket"
 
+      # Boot counting
+      "boot-complete.target"
+    ] ++ lib.optional config.boot.loader.systemd-boot.bootCounting.enable "systemd-bless-boot.service" ++ [
+
       # Hibernate / suspend.
       "hibernate.target"
       "suspend.target"
@@ -112,6 +118,7 @@ let
       "sleep.target"
       "hybrid-sleep.target"
       "systemd-hibernate.service"
+      "systemd-hibernate-clear.service"
       "systemd-hybrid-sleep.service"
       "systemd-suspend.service"
       "systemd-suspend-then-hibernate.service"
@@ -136,6 +143,16 @@ let
       "systemd-ask-password-wall.path"
       "systemd-ask-password-wall.service"
 
+      # Varlink APIs
+      "systemd-bootctl@.service"
+      "systemd-bootctl.socket"
+      "systemd-creds@.service"
+      "systemd-creds.socket"
+    ] ++ lib.optional cfg.package.withTpm2Tss [
+      "systemd-pcrlock@.service"
+      "systemd-pcrlock.socket"
+    ] ++ [
+
       # Slices / containers.
       "slices.target"
     ] ++ optionals cfg.package.withImportd [
@@ -158,6 +175,7 @@ let
     ] ++ optionals cfg.package.withHostnamed [
       "dbus-org.freedesktop.hostname1.service"
       "systemd-hostnamed.service"
+      "systemd-hostnamed.socket"
     ] ++ optionals cfg.package.withPortabled [
       "dbus-org.freedesktop.portable1.service"
       "systemd-portabled.service"
@@ -323,14 +341,6 @@ in
       '';
     };
 
-    enableUnifiedCgroupHierarchy = mkOption {
-      default = true;
-      type = types.bool;
-      description = ''
-        Whether to enable the unified cgroup hierarchy (cgroupsv2); see {manpage}`cgroups(7)`.
-      '';
-    };
-
     extraConfig = mkOption {
       default = "";
       type = types.lines;
@@ -676,12 +686,6 @@ in
     # https://github.com/systemd/systemd/pull/12226
     boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304);
 
-    boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
-
-    # Avoid potentially degraded system state due to
-    # "Userspace Out-Of-Memory (OOM) Killer was skipped because of a failed condition check (ConditionControlGroupController=v2)."
-    systemd.oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false;
-
     services.logrotate.settings = {
       "/var/log/btmp" = mapAttrs (_: mkDefault) {
         frequency = "monthly";
@@ -705,5 +709,10 @@ in
       (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
       (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
       (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
+      (mkRemovedOptionModule ["systemd" "enableUnifiedCgroupHierarchy"] ''
+          In 256 support for cgroup v1 ('legacy' and 'hybrid' hierarchies) is now considered obsolete and systemd by default will refuse to boot under it.
+          To forcibly reenable cgroup v1 support, you can set boot.kernelParams = [ "systemd.unified_cgroup_hierachy=0" "SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1" ].
+          NixOS does not officially support this configuration and might cause your system to be unbootable in future versions. You are on your own.
+      '')
     ];
 }
diff --git a/nixos/modules/system/boot/systemd/initrd.nix b/nixos/modules/system/boot/systemd/initrd.nix
index 6107a2594baf8..0caea104b1b52 100644
--- a/nixos/modules/system/boot/systemd/initrd.nix
+++ b/nixos/modules/system/boot/systemd/initrd.nix
@@ -70,6 +70,7 @@ let
     "systemd-tmpfiles-setup-dev.service"
     "systemd-tmpfiles-setup.service"
     "timers.target"
+    "tpm2.target"
     "umount.target"
     "systemd-bsod.service"
   ] ++ cfg.additionalUpstreamUnits;
@@ -102,7 +103,7 @@ let
   initrdBinEnv = pkgs.buildEnv {
     name = "initrd-bin-env";
     paths = map getBin cfg.initrdBin;
-    pathsToLink = ["/bin" "/sbin"];
+    pathsToLink = ["/bin"];
     postBuild = concatStringsSep "\n" (mapAttrsToList (n: v: "ln -sf '${v}' $out/bin/'${n}'") cfg.extraBin);
   };
 
@@ -111,8 +112,7 @@ let
     inherit (config.boot.initrd) compressor compressorArgs prepend;
     inherit (cfg) strip;
 
-    contents = map (path: { object = path; symlink = ""; }) (subtractLists cfg.suppressedStorePaths cfg.storePaths)
-      ++ mapAttrsToList (_: v: { object = v.source; symlink = v.target; }) (filterAttrs (_: v: v.enable) cfg.contents);
+    contents = lib.filter ({ source, ... }: !lib.elem source cfg.suppressedStorePaths) cfg.storePaths;
   };
 
 in {
@@ -160,7 +160,7 @@ in {
       description = "Set of files that have to be linked into the initrd";
       example = literalExpression ''
         {
-          "/etc/hostname".text = "mymachine";
+          "/etc/machine-id".source = /etc/machine-id;
         }
       '';
       default = {};
@@ -171,7 +171,7 @@ in {
       description = ''
         Store paths to copy into the initrd as well.
       '';
-      type = with types; listOf (oneOf [ singleLineStr package ]);
+      type = utils.systemdUtils.types.initrdStorePath;
       default = [];
     };
 
@@ -344,7 +344,8 @@ in {
     };
 
     enableTpm2 = mkOption {
-      default = true;
+      default = cfg.package.withTpm2Tss;
+      defaultText = "boot.initrd.systemd.package.withTpm2Tss";
       type = types.bool;
       description = ''
         Whether to enable TPM2 support in the initrd.
@@ -407,7 +408,7 @@ in {
         fsck = "${cfg.package.util-linux}/bin/fsck";
       };
 
-      managerEnvironment.PATH = "/bin:/sbin";
+      managerEnvironment.PATH = "/bin";
 
       contents = {
         "/tmp/.keep".text = "systemd requires the /tmp mount point in the initrd cpio archive";
@@ -416,7 +417,7 @@ in {
 
         "/etc/systemd/system.conf".text = ''
           [Manager]
-          DefaultEnvironment=PATH=/bin:/sbin
+          DefaultEnvironment=PATH=/bin
           ${cfg.extraConfig}
           ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
         '';
@@ -431,9 +432,9 @@ in {
         "/etc/shadow".text = "root:${if isBool cfg.emergencyAccess then optionalString (!cfg.emergencyAccess) "*" else cfg.emergencyAccess}:::::::";
 
         "/bin".source = "${initrdBinEnv}/bin";
-        "/sbin".source = "${initrdBinEnv}/sbin";
+        "/sbin".source = "${initrdBinEnv}/bin";
 
-        "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /sbin/modprobe";
+        "/etc/sysctl.d/nixos.conf".text = "kernel.modprobe = /bin/modprobe";
         "/etc/modprobe.d/systemd.conf".source = "${cfg.package}/lib/modprobe.d/systemd.conf";
         "/etc/modprobe.d/ubuntu.conf".source = pkgs.runCommand "initrd-kmod-blacklist-ubuntu" { } ''
           ${pkgs.buildPackages.perl}/bin/perl -0pe 's/## file: iwlwifi.conf(.+?)##/##/s;' $src > $out
@@ -443,6 +444,9 @@ in {
         "/etc/os-release".source = config.boot.initrd.osRelease;
         "/etc/initrd-release".source = config.boot.initrd.osRelease;
 
+        # For systemd-journald's _HOSTNAME field; needs to be set early, cannot be backfilled.
+        "/etc/hostname".text = config.networking.hostName;
+
       } // optionalAttrs (config.environment.etc ? "modprobe.d/nixos.conf") {
         "/etc/modprobe.d/nixos.conf".source = config.environment.etc."modprobe.d/nixos.conf".source;
       };
@@ -460,6 +464,7 @@ in {
         "${cfg.package}/lib/systemd/systemd-sulogin-shell"
         "${cfg.package}/lib/systemd/systemd-sysctl"
         "${cfg.package}/lib/systemd/systemd-bsod"
+        "${cfg.package}/lib/systemd/systemd-sysroot-fstab-check"
 
         # generators
         "${cfg.package}/lib/systemd/system-generators/systemd-debug-generator"
@@ -486,7 +491,8 @@ in {
         # fido2 support
         "${cfg.package}/lib/cryptsetup/libcryptsetup-token-systemd-fido2.so"
         "${pkgs.libfido2}/lib/libfido2.so.1"
-      ] ++ jobScripts;
+      ] ++ jobScripts
+      ++ map (c: builtins.removeAttrs c ["text"]) (builtins.attrValues cfg.contents);
 
       targets.initrd.aliases = ["default.target"];
       units =
diff --git a/nixos/modules/system/boot/systemd/journald.nix b/nixos/modules/system/boot/systemd/journald.nix
index 586de87dbc8ea..180a5cf6c396b 100644
--- a/nixos/modules/system/boot/systemd/journald.nix
+++ b/nixos/modules/system/boot/systemd/journald.nix
@@ -96,6 +96,7 @@ in {
       "systemd-journald@.service"
       "systemd-journal-flush.service"
       "systemd-journal-catalog-update.service"
+      "systemd-journald-sync@.service"
       ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [
       "systemd-journald-dev-log.socket"
       "syslog.socket"
diff --git a/nixos/modules/system/boot/systemd/nspawn.nix b/nixos/modules/system/boot/systemd/nspawn.nix
index 11fbb88838e10..e9bf82c462a95 100644
--- a/nixos/modules/system/boot/systemd/nspawn.nix
+++ b/nixos/modules/system/boot/systemd/nspawn.nix
@@ -127,6 +127,9 @@ in {
         })
         {
           systemd.targets.multi-user.wants = [ "machines.target" ];
+          systemd.services."systemd-nspawn@".environment = {
+            SYSTEMD_NSPAWN_UNIFIED_HIERARCHY = mkDefault "1";
+          };
         }
       ];
 }
diff --git a/nixos/modules/system/boot/systemd/shutdown.nix b/nixos/modules/system/boot/systemd/shutdown.nix
index 5c2525a57b4be..48477954e20c7 100644
--- a/nixos/modules/system/boot/systemd/shutdown.nix
+++ b/nixos/modules/system/boot/systemd/shutdown.nix
@@ -2,10 +2,7 @@
 
   cfg = config.systemd.shutdownRamfs;
 
-  ramfsContents = let
-    storePaths = map (p: "${p}\n") cfg.storePaths;
-    contents = lib.mapAttrsToList (_: v: "${v.source}\n${v.target}") (lib.filterAttrs (_: v: v.enable) cfg.contents);
-  in pkgs.writeText "shutdown-ramfs-contents" (lib.concatStringsSep "\n" (storePaths ++ contents));
+  ramfsContents = pkgs.writeText "shutdown-ramfs-contents.json" (builtins.toJSON cfg.storePaths);
 
 in {
   options.systemd.shutdownRamfs = {
@@ -24,7 +21,7 @@ in {
       description = ''
         Store paths to copy into the shutdown ramfs as well.
       '';
-      type = lib.types.listOf lib.types.singleLineStr;
+      type = utils.systemdUtils.types.initrdStorePath;
       default = [];
     };
   };
@@ -35,7 +32,8 @@ in {
       "/etc/initrd-release".source = config.environment.etc.os-release.source;
       "/etc/os-release".source = config.environment.etc.os-release.source;
     };
-    systemd.shutdownRamfs.storePaths = [pkgs.runtimeShell "${pkgs.coreutils}/bin"];
+    systemd.shutdownRamfs.storePaths = [pkgs.runtimeShell "${pkgs.coreutils}/bin"]
+      ++ map (c: builtins.removeAttrs c ["text"]) (builtins.attrValues cfg.contents);
 
     systemd.mounts = [{
       what = "tmpfs";
diff --git a/nixos/modules/system/boot/systemd/sysusers.nix b/nixos/modules/system/boot/systemd/sysusers.nix
index 476251e140456..8d401436daa17 100644
--- a/nixos/modules/system/boot/systemd/sysusers.nix
+++ b/nixos/modules/system/boot/systemd/sysusers.nix
@@ -5,6 +5,8 @@ let
   cfg = config.systemd.sysusers;
   userCfg = config.users;
 
+  systemUsers = lib.filterAttrs (_username: opts: !opts.isNormalUser) userCfg.users;
+
   sysusersConfig = pkgs.writeTextDir "00-nixos.conf" ''
     # Type Name ID GECOS Home directory Shell
 
@@ -16,7 +18,7 @@ let
         in
           ''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}''
       )
-      userCfg.users)
+      systemUsers)
     }
 
     # Groups
@@ -30,32 +32,12 @@ let
     }
   '';
 
-  staticSysusersCredentials = pkgs.runCommand "static-sysusers-credentials" { } ''
-    mkdir $out; cd $out
-    ${lib.concatLines (
-      (lib.mapAttrsToList
-        (username: opts: "echo -n '${opts.initialHashedPassword}' > 'passwd.hashed-password.${username}'")
-        (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
-        ++
-      (lib.mapAttrsToList
-        (username: opts: "echo -n '${opts.initialPassword}' > 'passwd.plaintext-password.${username}'")
-        (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
-        ++
-      (lib.mapAttrsToList
-        (username: opts: "cat '${opts.hashedPasswordFile}' > 'passwd.hashed-password.${username}'")
-        (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users))
-      )
-    }
-  '';
-
-  staticSysusers = pkgs.runCommand "static-sysusers"
-    {
-      nativeBuildInputs = [ pkgs.systemd ];
-    } ''
-    mkdir $out
-    export CREDENTIALS_DIRECTORY=${staticSysusersCredentials}
-    systemd-sysusers --root $out ${sysusersConfig}/00-nixos.conf
-  '';
+  immutableEtc = config.system.etc.overlay.enable && !config.system.etc.overlay.mutable;
+  # The location of the password files when using an immutable /etc.
+  immutablePasswordFilesLocation = "/var/lib/nixos/etc";
+  passwordFilesLocation = if immutableEtc then immutablePasswordFilesLocation else "/etc";
+  # The filenames created by systemd-sysusers.
+  passwordFiles = [ "passwd" "group" "shadow" "gshadow" ];
 
 in
 
@@ -90,95 +72,114 @@ in
         assertion = config.users.mutableUsers -> config.system.etc.overlay.enable;
         message = "config.users.mutableUsers requires config.system.etc.overlay.enable.";
       }
-    ];
-
-    systemd = lib.mkMerge [
-      ({
-
-        # Create home directories, do not create /var/empty even if that's a user's
-        # home.
-        tmpfiles.settings.home-directories = lib.mapAttrs'
-          (username: opts: lib.nameValuePair opts.home {
-            d = {
-              mode = opts.homeMode;
-              user = username;
-              group = opts.group;
-            };
-          })
-          (lib.filterAttrs (_username: opts: opts.home != "/var/empty") userCfg.users);
-
-        # Create uid/gid marker files for those without an explicit id
-        tmpfiles.settings.nixos-uid = lib.mapAttrs'
-          (username: opts: lib.nameValuePair "/var/lib/nixos/uid/${username}" {
-            f = {
-              user = username;
-            };
-          })
-          (lib.filterAttrs (_username: opts: opts.uid == null) userCfg.users);
-
-        tmpfiles.settings.nixos-gid = lib.mapAttrs'
-          (groupname: opts: lib.nameValuePair "/var/lib/nixos/gid/${groupname}" {
-            f = {
-              group = groupname;
-            };
-          })
-          (lib.filterAttrs (_groupname: opts: opts.gid == null) userCfg.groups);
+    ] ++ (lib.mapAttrsToList
+      (_username: opts: {
+        assertion = !opts.isNormalUser;
+        message = "systemd-sysusers doesn't create normal users. You can currently only use it to create system users.";
       })
+      userCfg.users)
+    ++ lib.mapAttrsToList
+      (username: opts: {
+        assertion = (opts.password == opts.initialPassword || opts.password == null) &&
+          (opts.hashedPassword == opts.initialHashedPassword || opts.hashedPassword == null);
+        message = "${username} uses password or hashedPassword. systemd-sysupdate only supports initial passwords. It'll never update your passwords.";
+      })
+      systemUsers;
+
+    systemd = {
+
+      # Create home directories, do not create /var/empty even if that's a user's
+      # home.
+      tmpfiles.settings.home-directories = lib.mapAttrs'
+        (username: opts: lib.nameValuePair opts.home {
+          d = {
+            mode = opts.homeMode;
+            user = username;
+            group = opts.group;
+          };
+        })
+        (lib.filterAttrs (_username: opts: opts.home != "/var/empty") systemUsers);
+
+      # Create uid/gid marker files for those without an explicit id
+      tmpfiles.settings.nixos-uid = lib.mapAttrs'
+        (username: opts: lib.nameValuePair "/var/lib/nixos/uid/${username}" {
+          f = {
+            user = username;
+          };
+        })
+        (lib.filterAttrs (_username: opts: opts.uid == null) systemUsers);
 
-      (lib.mkIf config.users.mutableUsers {
-        additionalUpstreamSystemUnits = [
-          "systemd-sysusers.service"
-        ];
-
-        services.systemd-sysusers = {
-          # Enable switch-to-configuration to restart the service.
-          unitConfig.ConditionNeedsUpdate = [ "" ];
-          requiredBy = [ "sysinit-reactivation.target" ];
-          before = [ "sysinit-reactivation.target" ];
-          restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ];
-
-          serviceConfig = {
-            LoadCredential = lib.mapAttrsToList
-              (username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}")
-              (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) userCfg.users);
-            SetCredential = (lib.mapAttrsToList
-              (username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}")
-              (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) userCfg.users))
-            ++
-            (lib.mapAttrsToList
-              (username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}")
-              (lib.filterAttrs (_username: opts: opts.initialPassword != null) userCfg.users))
-            ;
+      tmpfiles.settings.nixos-gid = lib.mapAttrs'
+        (groupname: opts: lib.nameValuePair "/var/lib/nixos/gid/${groupname}" {
+          f = {
+            group = groupname;
           };
+        })
+        (lib.filterAttrs (_groupname: opts: opts.gid == null) userCfg.groups);
+
+      additionalUpstreamSystemUnits = [
+        "systemd-sysusers.service"
+      ];
+
+      services.systemd-sysusers = {
+        # Enable switch-to-configuration to restart the service.
+        unitConfig.ConditionNeedsUpdate = [ "" ];
+        requiredBy = [ "sysinit-reactivation.target" ];
+        before = [ "sysinit-reactivation.target" ];
+        restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ];
+
+        serviceConfig = {
+          # When we have an immutable /etc we cannot write the files directly
+          # to /etc so we write it to a different directory and symlink them
+          # into /etc.
+          #
+          # We need to explicitly list the config file, otherwise
+          # systemd-sysusers cannot find it when we also pass another flag.
+          ExecStart = lib.mkIf immutableEtc
+            [ "" "${config.systemd.package}/bin/systemd-sysusers --root ${builtins.dirOf immutablePasswordFilesLocation} /etc/sysusers.d/00-nixos.conf" ];
+
+          # Make the source files writable before executing sysusers.
+          ExecStartPre = lib.mkIf (!userCfg.mutableUsers)
+            (lib.map
+              (file: "-${pkgs.util-linux}/bin/umount ${passwordFilesLocation}/${file}")
+              passwordFiles);
+          # Make the source files read-only after sysusers has finished.
+          ExecStartPost = lib.mkIf (!userCfg.mutableUsers)
+            (lib.map
+              (file: "${pkgs.util-linux}/bin/mount --bind -o ro ${passwordFilesLocation}/${file} ${passwordFilesLocation}/${file}")
+              passwordFiles);
+
+          LoadCredential = lib.mapAttrsToList
+            (username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}")
+            (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) systemUsers);
+          SetCredential = (lib.mapAttrsToList
+            (username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}")
+            (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) systemUsers))
+          ++
+          (lib.mapAttrsToList
+            (username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}")
+            (lib.filterAttrs (_username: opts: opts.initialPassword != null) systemUsers))
+          ;
         };
-      })
-    ];
+      };
 
-    environment.etc = lib.mkMerge [
-      (lib.mkIf (!userCfg.mutableUsers) {
-        "passwd" = {
-          source = "${staticSysusers}/etc/passwd";
-          mode = "0644";
-        };
-        "group" = {
-          source = "${staticSysusers}/etc/group";
-          mode = "0644";
-        };
-        "shadow" = {
-          source = "${staticSysusers}/etc/shadow";
-          mode = "0000";
-        };
-        "gshadow" = {
-          source = "${staticSysusers}/etc/gshadow";
-          mode = "0000";
-        };
-      })
+    };
 
-      (lib.mkIf userCfg.mutableUsers {
+    environment.etc = lib.mkMerge [
+      ({
         "sysusers.d".source = sysusersConfig;
       })
-    ];
 
+      # Statically create the symlinks to immutablePasswordFilesLocation when
+      # using an immutable /etc because we will not be able to do it at
+      # runtime!
+      (lib.mkIf immutableEtc (lib.listToAttrs (lib.map
+        (file: lib.nameValuePair file {
+          source = "${immutablePasswordFilesLocation}/${file}";
+          mode = "direct-symlink";
+        })
+        passwordFiles)))
+    ];
   };
 
   meta.maintainers = with lib.maintainers; [ nikstur ];
diff --git a/nixos/modules/system/boot/systemd/tmpfiles.nix b/nixos/modules/system/boot/systemd/tmpfiles.nix
index ded13728017d1..af37fb07d29bc 100644
--- a/nixos/modules/system/boot/systemd/tmpfiles.nix
+++ b/nixos/modules/system/boot/systemd/tmpfiles.nix
@@ -200,6 +200,10 @@ in
           rm -f $out/${removePrefix "tmpfiles.d/" name}
         '') config.system.build.etc.passthru.targets;
       }) + "/*";
+      "mtab" = {
+        mode = "direct-symlink";
+        source = "/proc/mounts";
+      };
     };
 
     systemd.tmpfiles.packages = [
@@ -244,13 +248,11 @@ in
       "L+ /nix/var/nix/gcroots/booted-system 0755 root root - /run/booted-system"
       "d  /run/lock                          0755 root root - -"
       "d  /var/db                            0755 root root - -"
-      "L  /etc/mtab                          -    -    -    - ../proc/mounts"
       "L  /var/lock                          -    -    -    - ../run/lock"
       # Boot-time cleanup
       "R! /etc/group.lock                    -    -    -    - -"
       "R! /etc/passwd.lock                   -    -    -    - -"
       "R! /etc/shadow.lock                   -    -    -    - -"
-      "R! /etc/mtab*                         -    -    -    - -"
       "R! /nix/var/nix/gcroots/tmp           -    -    -    - -"
       "R! /nix/var/nix/temproots             -    -    -    - -"
     ];
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
index 87932075f3679..69f4ab92548f0 100644
--- a/nixos/modules/system/etc/etc.nix
+++ b/nixos/modules/system/etc/etc.nix
@@ -247,6 +247,30 @@ in
           --options lowerdir=$tmpMetadataMount::${config.system.build.etcBasedir},${etcOverlayOptions} \
           $tmpEtcMount
 
+        # Before moving the new /etc overlay under the old /etc, we have to
+        # move mounts on top of /etc to the new /etc mountpoint.
+        findmnt /etc --submounts --list --noheading --kernel --output TARGET | while read -r mountPoint; do
+          if [[ "$mountPoint" = "/etc" ]]; then
+            continue
+          fi
+
+          tmpMountPoint="$tmpEtcMount/''${mountPoint:5}"
+            ${if config.system.etc.overlay.mutable then ''
+              if [[ -f "$mountPoint" ]]; then
+                touch "$tmpMountPoint"
+              elif [[ -d "$mountPoint" ]]; then
+                mkdir -p "$tmpMountPoint"
+              fi
+            '' else ''
+              if [[ ! -e "$tmpMountPoint" ]]; then
+                echo "Skipping undeclared mountpoint in environment.etc: $mountPoint"
+                continue
+              fi
+            ''
+          }
+          mount --bind "$mountPoint" "$tmpMountPoint"
+        done
+
         # Move the new temporary /etc mount underneath the current /etc mount.
         #
         # This should eventually use util-linux to perform this move beneath,
@@ -255,8 +279,7 @@ in
         ${pkgs.move-mount-beneath}/bin/move-mount --move --beneath $tmpEtcMount /etc
 
         # Unmount the top /etc mount to atomically reveal the new mount.
-        umount /etc
-
+        umount --recursive /etc
       fi
     '' else ''
       # Set up the statically computed bits of /etc.
diff --git a/nixos/modules/tasks/filesystems/btrfs.nix b/nixos/modules/tasks/filesystems/btrfs.nix
index 17e3a274c0e92..7329a79813379 100644
--- a/nixos/modules/tasks/filesystems/btrfs.nix
+++ b/nixos/modules/tasks/filesystems/btrfs.nix
@@ -1,8 +1,19 @@
 { config, lib, pkgs, utils, ... }:
 
-with lib;
-
 let
+  inherit (lib)
+    mkEnableOption
+    mkOption
+    types
+    mkMerge
+    mkIf
+    optionals
+    mkDefault
+    nameValuePair
+    listToAttrs
+    filterAttrs
+    mapAttrsToList
+    foldl';
 
   inInitrd = config.boot.initrd.supportedFilesystems.btrfs or false;
   inSystem = config.boot.supportedFilesystems.btrfs or false;
@@ -27,8 +38,9 @@ in
         description = ''
           List of paths to btrfs filesystems to regularly call {command}`btrfs scrub` on.
           Defaults to all mount points with btrfs filesystems.
-          If you mount a filesystem multiple times or additionally mount subvolumes,
-          you need to manually specify this list to avoid scrubbing multiple times.
+          Note that if you have filesystems that span multiple devices (e.g. RAID), you should
+          take care to use the same device for any given mount point and let btrfs take care
+          of automatically mounting the rest, in order to avoid scrubbing the same data multiple times.
         '';
       };
 
@@ -97,12 +109,18 @@ in
         }
       ];
 
-      # This will yield duplicated units if the user mounts a filesystem multiple times
-      # or additionally mounts subvolumes, but going the other way around via devices would
-      # yield duplicated units when a filesystem spans multiple devices.
-      # This way around seems like the more sensible default.
-      services.btrfs.autoScrub.fileSystems = mkDefault (mapAttrsToList (name: fs: fs.mountPoint)
-      (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems));
+      # This will remove duplicated units from either having a filesystem mounted multiple
+      # time, or additionally mounted subvolumes, as well as having a filesystem span
+      # multiple devices (provided the same device is used to mount said filesystem).
+      services.btrfs.autoScrub.fileSystems =
+      let
+        isDeviceInList = list: device: builtins.filter (e: e.device == device) list != [ ];
+
+        uniqueDeviceList = foldl' (acc: e: if isDeviceInList acc e.device then acc else acc ++ [ e ]) [ ];
+      in
+      mkDefault (map (e: e.mountPoint)
+        (uniqueDeviceList (mapAttrsToList (name: fs: { mountPoint = fs.mountPoint; device = fs.device; })
+          (filterAttrs (name: fs: fs.fsType == "btrfs") config.fileSystems))));
 
       # TODO: Did not manage to do it via the usual btrfs-scrub@.timer/.service
       # template units due to problems enabling the parameterized units,
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index 77730178422c3..f6d8848816d4d 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -6,9 +6,8 @@
 
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
+  inherit (lib) mkDefault mkIf;
   cfg = config.ec2;
 in
 
@@ -107,5 +106,5 @@ in
     # (e.g. it depends on GTK).
     services.udisks2.enable = false;
   };
-  meta.maintainers = with maintainers; [ arianvp ];
+  meta.maintainers = with lib.maintainers; [ arianvp ];
 }
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index 8a0894ed85c3d..5ffec126d8f0f 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -69,7 +69,7 @@ in
         type = types.bool;
         default = false;
         description = ''
-          **Deprecated**, please use virtualisation.containers.cdi.dynamic.nvidia.enable instead.
+          **Deprecated**, please use hardware.nvidia-container-toolkit.enable instead.
 
           Enable nvidia-docker wrapper, supporting NVIDIA GPUs inside docker containers.
         '';
@@ -186,7 +186,7 @@ in
       # wrappers.
       warnings = lib.optionals (cfg.enableNvidia && (lib.strings.versionAtLeast cfg.package.version "25")) [
         ''
-          You have set virtualisation.docker.enableNvidia. This option is deprecated, please set virtualisation.containers.cdi.dynamic.nvidia.enable instead.
+          You have set virtualisation.docker.enableNvidia. This option is deprecated, please set hardware.nvidia-container-toolkit.enable instead.
         ''
       ];
 
diff --git a/nixos/modules/virtualisation/ec2-data.nix b/nixos/modules/virtualisation/ec2-data.nix
index 3414c5a1fc9de..0b9d098dbab76 100644
--- a/nixos/modules/virtualisation/ec2-data.nix
+++ b/nixos/modules/virtualisation/ec2-data.nix
@@ -79,7 +79,7 @@ with lib;
             # ec2-get-console-output.
             echo "-----BEGIN SSH HOST KEY FINGERPRINTS-----" > /dev/console
             for i in /etc/ssh/ssh_host_*_key.pub; do
-                ${config.programs.ssh.package}/bin/ssh-keygen -l -f $i > /dev/console
+                ${config.programs.ssh.package}/bin/ssh-keygen -l -f $i || true > /dev/console
             done
             echo "-----END SSH HOST KEY FINGERPRINTS-----" > /dev/console
           '';
diff --git a/nixos/modules/virtualisation/includes-to-excludes.py b/nixos/modules/virtualisation/includes-to-excludes.py
deleted file mode 100644
index 05ef9c0f23b91..0000000000000
--- a/nixos/modules/virtualisation/includes-to-excludes.py
+++ /dev/null
@@ -1,86 +0,0 @@
-
-# Convert a list of strings to a regex that matches everything but those strings
-# ... and it had to be a POSIX regex; no negative lookahead :(
-# This is a workaround for erofs supporting only exclude regex, not an include list
-
-import sys
-import re
-from collections import defaultdict
-
-# We can configure this script to match in different ways if we need to.
-# The regex got too long for the argument list, so we had to truncate the
-# hashes and use MATCH_STRING_PREFIX. That's less accurate, and might pick up some
-# garbage like .lock files, but only if the sandbox doesn't hide those. Even
-# then it should be harmless.
-
-# Produce the negation of ^a$
-MATCH_EXACTLY = ".+"
-# Produce the negation of ^a
-MATCH_STRING_PREFIX = "//X" # //X should be epsilon regex instead. Not supported??
-# Produce the negation of ^a/?
-MATCH_SUBPATHS = "[^/].*$"
-
-# match_end = MATCH_SUBPATHS
-match_end = MATCH_STRING_PREFIX
-# match_end = MATCH_EXACTLY
-
-def chars_to_inverted_class(letters):
-    assert len(letters) > 0
-    letters = list(letters)
-
-    s = "[^"
-
-    if "]" in letters:
-        s += "]"
-        letters.remove("]")
-
-    final = ""
-    if "-" in letters:
-        final = "-"
-        letters.remove("-")
-
-    s += "".join(letters)
-
-    s += final
-
-    s += "]"
-
-    return s
-
-# There's probably at least one bug in here, but it seems to works well enough
-# for filtering store paths.
-def strings_to_inverted_regex(strings):
-    s = "("
-
-    # Match anything that starts with the wrong character
-
-    chars = defaultdict(list)
-
-    for item in strings:
-        if item != "":
-            chars[item[0]].append(item[1:])
-
-    if len(chars) == 0:
-        s += match_end
-    else:
-        s += chars_to_inverted_class(chars)
-
-    # Now match anything that starts with the right char, but then goes wrong
-
-    for char, sub in chars.items():
-        s += "|(" + re.escape(char) + strings_to_inverted_regex(sub) + ")"
-
-    s += ")"
-    return s
-
-if __name__ == "__main__":
-    stdin_lines = []
-    for line in sys.stdin:
-        if line.strip() != "":
-            stdin_lines.append(line.strip())
-
-    print("^" + strings_to_inverted_regex(stdin_lines))
-
-# Test:
-# (echo foo; echo fo/; echo foo/; echo foo/ba/r; echo b; echo az; echo az/; echo az/a; echo ab; echo ab/a; echo ab/; echo abc; echo abcde; echo abb; echo ac; echo b) | grep -vE "$((echo ab; echo az; echo foo;) | python includes-to-excludes.py | tee /dev/stderr )"
-# should print ab, az, foo and their subpaths
diff --git a/nixos/modules/virtualisation/incus-agent.nix b/nixos/modules/virtualisation/incus-agent.nix
new file mode 100644
index 0000000000000..bfb9eeb75d333
--- /dev/null
+++ b/nixos/modules/virtualisation/incus-agent.nix
@@ -0,0 +1,41 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  cfg = config.virtualisation.incus.agent;
+in
+{
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  options = {
+    virtualisation.incus.agent.enable = lib.mkEnableOption "Incus agent";
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.udev.packages = [ config.virtualisation.incus.package.agent_loader ];
+    systemd.packages = [ config.virtualisation.incus.package.agent_loader ];
+
+    systemd.services.incus-agent = {
+      enable = true;
+      wantedBy = [ "multi-user.target" ];
+
+      path = [
+        pkgs.kmod
+        pkgs.util-linux
+
+        # allow `incus exec` to find system binaries
+        "/run/current-system/sw"
+      ];
+
+      # avoid killing nixos-rebuild switch when executed through incus exec
+      restartIfChanged = false;
+      stopIfChanged = false;
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/incus-virtual-machine.nix b/nixos/modules/virtualisation/incus-virtual-machine.nix
new file mode 100644
index 0000000000000..d51e251aaba95
--- /dev/null
+++ b/nixos/modules/virtualisation/incus-virtual-machine.nix
@@ -0,0 +1,61 @@
+{
+  config,
+  lib,
+  pkgs,
+  ...
+}:
+
+let
+  serialDevice = if pkgs.stdenv.hostPlatform.isx86 then "ttyS0" else "ttyAMA0";
+in
+{
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
+  imports = [
+    ./lxc-instance-common.nix
+
+    ../profiles/qemu-guest.nix
+  ];
+
+  config = {
+    system.build.qemuImage = import ../../lib/make-disk-image.nix {
+      inherit pkgs lib config;
+
+      partitionTableType = "efi";
+      format = "qcow2-compressed";
+      copyChannel = true;
+    };
+
+    fileSystems = {
+      "/" = {
+        device = "/dev/disk/by-label/nixos";
+        autoResize = true;
+        fsType = "ext4";
+      };
+      "/boot" = {
+        device = "/dev/disk/by-label/ESP";
+        fsType = "vfat";
+      };
+    };
+
+    boot.growPartition = true;
+    boot.loader.systemd-boot.enable = true;
+
+    # image building needs to know what device to install bootloader on
+    boot.loader.grub.device = "/dev/vda";
+
+    boot.kernelParams = [
+      "console=tty1"
+      "console=${serialDevice}"
+    ];
+
+    # CPU hotplug
+    services.udev.extraRules = ''
+      SUBSYSTEM=="cpu", CONST{arch}=="x86-64", TEST=="online", ATTR{online}=="0", ATTR{online}="1"
+    '';
+
+    virtualisation.incus.agent.enable = lib.mkDefault true;
+  };
+}
diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix
index 2b69a7a076585..f4c904ff670c8 100644
--- a/nixos/modules/virtualisation/incus.nix
+++ b/nixos/modules/virtualisation/incus.nix
@@ -55,6 +55,10 @@ let
         xdelta
         xz
       ]
+      ++ lib.optionals (lib.versionAtLeast cfg.package.version "6.3.0") [
+        skopeo
+        umoci
+      ]
       ++ lib.optionals config.security.apparmor.enable [
         apparmor-bin-utils
 
@@ -109,10 +113,11 @@ let
   environment = lib.mkMerge [
     {
       INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config";
-      INCUS_OVMF_PATH = ovmf;
       INCUS_USBIDS_PATH = "${pkgs.hwdata}/share/hwdata/usb.ids";
       PATH = lib.mkForce serverBinPath;
     }
+    (lib.mkIf (lib.versionOlder cfg.package.version "6.3.0") { INCUS_OVMF_PATH = ovmf; })
+    (lib.mkIf (lib.versionAtLeast cfg.package.version "6.3.0") { INCUS_EDK2_PATH = ovmf; })
     (lib.mkIf (cfg.ui.enable) { "INCUS_UI" = cfg.ui.package; })
   ];
 
diff --git a/nixos/modules/virtualisation/lxc-container.nix b/nixos/modules/virtualisation/lxc-container.nix
index 95e3083ff9eda..0b528496bb337 100644
--- a/nixos/modules/virtualisation/lxc-container.nix
+++ b/nixos/modules/virtualisation/lxc-container.nix
@@ -74,7 +74,7 @@
       ];
     };
 
-    system.build.installBootLoader = pkgs.writeScript "install-lxd-sbin-init.sh" ''
+    system.build.installBootLoader = pkgs.writeScript "install-lxc-sbin-init.sh" ''
       #!${pkgs.runtimeShell}
       ${pkgs.coreutils}/bin/ln -fs "$1/${initScript}" /sbin/init
     '';
diff --git a/nixos/modules/virtualisation/lxc-image-metadata.nix b/nixos/modules/virtualisation/lxc-image-metadata.nix
index 38d955798f3e0..eb14f9dc5fc1b 100644
--- a/nixos/modules/virtualisation/lxc-image-metadata.nix
+++ b/nixos/modules/virtualisation/lxc-image-metadata.nix
@@ -46,6 +46,10 @@ let
   else { files = []; properties = {}; };
 
 in {
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
   options = {
     virtualisation.lxc = {
       templates = lib.mkOption {
diff --git a/nixos/modules/virtualisation/lxc-instance-common.nix b/nixos/modules/virtualisation/lxc-instance-common.nix
index d6a0e05fb1c96..77285e5297263 100644
--- a/nixos/modules/virtualisation/lxc-instance-common.nix
+++ b/nixos/modules/virtualisation/lxc-instance-common.nix
@@ -1,6 +1,10 @@
 {lib, ...}:
 
 {
+  meta = {
+    maintainers = lib.teams.lxc.members;
+  };
+
   imports = [
     ./lxc-image-metadata.nix
 
diff --git a/nixos/modules/virtualisation/lxd-agent.nix b/nixos/modules/virtualisation/lxd-agent.nix
index d319371478481..4bcec117aba97 100644
--- a/nixos/modules/virtualisation/lxd-agent.nix
+++ b/nixos/modules/virtualisation/lxd-agent.nix
@@ -45,10 +45,6 @@ let
     chown -R root:root "$PREFIX"
   '';
 in {
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
   options = {
     virtualisation.lxd.agent.enable = lib.mkEnableOption "LXD agent";
   };
diff --git a/nixos/modules/virtualisation/lxd-virtual-machine.nix b/nixos/modules/virtualisation/lxd-virtual-machine.nix
index 2768e7c259662..138a858997188 100644
--- a/nixos/modules/virtualisation/lxd-virtual-machine.nix
+++ b/nixos/modules/virtualisation/lxd-virtual-machine.nix
@@ -6,10 +6,6 @@ let
     then "ttyS0"
     else "ttyAMA0"; # aarch64
 in {
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
   imports = [
     ./lxc-instance-common.nix
 
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index 4c94b3dfe946d..bdcd60a0560f4 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -6,10 +6,6 @@ let
   cfg = config.virtualisation.lxd;
   preseedFormat = pkgs.formats.yaml {};
 in {
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
   imports = [
     (lib.mkRemovedOptionModule [ "virtualisation" "lxd" "zfsPackage" ] "Override zfs in an overlay instead to override it globally")
   ];
@@ -166,10 +162,6 @@ in {
       };
     };
 
-    # TODO: remove once LXD gets proper support for cgroupsv2
-    # (currently most of the e.g. CPU accounting stuff doesn't work)
-    systemd.enableUnifiedCgroupHierarchy = false;
-
     systemd.sockets.lxd = {
       description = "LXD UNIX socket";
       wantedBy = [ "sockets.target" ];
@@ -214,6 +206,7 @@ in {
         LimitNOFILE = "1048576";
         LimitNPROC = "infinity";
         TasksMax = "infinity";
+        Delegate = true; # LXD needs to manage cgroups in its subtree
 
         # By default, `lxd` loads configuration files from hard-coded
         # `/usr/share/lxc/config` - since this is a no-go for us, we have to
diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix
index 8892f2f154640..1f96fa2de491c 100644
--- a/nixos/modules/virtualisation/nixos-containers.nix
+++ b/nixos/modules/virtualisation/nixos-containers.nix
@@ -842,8 +842,8 @@ in
             optionalAttrs containerConfig.autoStart
               {
                 wantedBy = [ "machines.target" ];
-                wants = [ "network.target" ];
-                after = [ "network.target" ];
+                wants = [ "network.target" ] ++ (map (i: "sys-subsystem-net-devices-${i}.device") cfg.interfaces);
+                after = [ "network.target" ] ++ (map (i: "sys-subsystem-net-devices-${i}.device") cfg.interfaces);
                 restartTriggers = [
                   containerConfig.path
                   config.environment.etc."${configurationDirectoryName}/${name}.conf".source
diff --git a/nixos/modules/virtualisation/oci-containers.nix b/nixos/modules/virtualisation/oci-containers.nix
index f4fa934231798..bf982ba0d7e2e 100644
--- a/nixos/modules/virtualisation/oci-containers.nix
+++ b/nixos/modules/virtualisation/oci-containers.nix
@@ -119,7 +119,7 @@ let
             For more details and a full list of logging drivers, refer to respective backends documentation.
 
             For Docker:
-            [Docker engine documentation](https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver)
+            [Docker engine documentation](https://docs.docker.com/engine/logging/configure/)
 
             For Podman:
             Refer to the docker-run(1) man page.
@@ -148,12 +148,17 @@ let
             somewhere within the specified `hostPort` range.
             Example: `1234-1236:1234/tcp`
 
+            Publishing a port bypasses the NixOS firewall. If the port is not
+            supposed to be shared on the network, make sure to publish the
+            port to localhost.
+            Example: `127.0.0.1:1234:1234`
+
             Refer to the
-            [Docker engine documentation](https://docs.docker.com/engine/reference/run/#expose-incoming-ports) for full details.
+            [Docker engine documentation](https://docs.docker.com/engine/network/#published-ports) for full details.
           '';
           example = literalExpression ''
             [
-              "8080:9000"
+              "127.0.0.1:8080:9000"
             ]
           '';
         };
@@ -179,7 +184,7 @@ let
             would be difficult with an attribute set.  There are
             also a variety of mount options available as a third
             field; please refer to the
-            [docker engine documentation](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) for details.
+            [docker engine documentation](https://docs.docker.com/engine/storage/volumes/) for details.
           '';
           example = literalExpression ''
             [
diff --git a/nixos/modules/virtualisation/podman/default.nix b/nixos/modules/virtualisation/podman/default.nix
index deb0b4d2c5bd7..d0f4ac7f66dac 100644
--- a/nixos/modules/virtualisation/podman/default.nix
+++ b/nixos/modules/virtualisation/podman/default.nix
@@ -82,7 +82,7 @@ in
       type = types.bool;
       default = false;
       description = ''
-        **Deprecated**, please use virtualisation.containers.cdi.dynamic.nvidia.enable instead.
+        **Deprecated**, please use hardware.nvidia-container-toolkit.enable instead.
 
         Enable use of NVidia GPUs from within podman containers.
       '';
diff --git a/nixos/modules/virtualisation/proxmox-image.nix b/nixos/modules/virtualisation/proxmox-image.nix
index 01ad86c08cd78..d390c78432ae7 100644
--- a/nixos/modules/virtualisation/proxmox-image.nix
+++ b/nixos/modules/virtualisation/proxmox-image.nix
@@ -243,18 +243,18 @@ with lib;
         }).overrideAttrs ( super: rec {
           # Check https://github.com/proxmox/pve-qemu/tree/master for the version
           # of qemu and patch to use
-          version = "8.1.5";
+          version = "9.0.0";
           src = pkgs.fetchurl {
             url = "https://download.qemu.org/qemu-${version}.tar.xz";
-            hash = "sha256-l2Ox7+xP1JeWtQgNCINRLXDLY4nq1lxmHMNoalIjKJY=";
+            hash = "sha256-MnCKxmww2MiSYz6paMdxwcdtWX1w3erSGg0izPOG2mk=";
           };
           patches = [
             # Proxmox' VMA tool is published as a particular patch upon QEMU
             "${pkgs.fetchFromGitHub {
               owner = "proxmox";
               repo = "pve-qemu";
-              rev = "71dd2d48f9122e60e4c0a8480122a27aab15dc70";
-              hash = "sha256-Q8AxNv4geDdlbVIWphRO5P3ESo0SGgvUpVPmPJzubJM=";
+              rev = "14afbdd55f04d250bd679ca1ad55d3f47cd9d4c8";
+              hash = "sha256-lSJQA5SHIHfxJvMLIID2drv2H43crTPMNIlIT37w9Nc=";
             }}/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch"
           ];
 
diff --git a/nixos/modules/virtualisation/proxmox-lxc.nix b/nixos/modules/virtualisation/proxmox-lxc.nix
index ff1c0972166cf..b2f9d0635fd16 100644
--- a/nixos/modules/virtualisation/proxmox-lxc.nix
+++ b/nixos/modules/virtualisation/proxmox-lxc.nix
@@ -1,9 +1,19 @@
-{ config, pkgs, lib, ... }:
+{
+  config,
+  pkgs,
+  lib,
+  ...
+}:
 
 with lib;
 
 {
   options.proxmoxLXC = {
+    enable = mkOption {
+      default = true;
+      type = types.bool;
+      description = "Whether to enable the Proxmox VE LXC module.";
+    };
     privileged = mkOption {
       type = types.bool;
       default = false;
@@ -35,21 +45,37 @@ with lib;
     let
       cfg = config.proxmoxLXC;
     in
-    {
+    mkIf cfg.enable {
       system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
-        storeContents = [{
-          object = config.system.build.toplevel;
-          symlink = "none";
-        }];
+        storeContents = [
+          {
+            object = config.system.build.toplevel;
+            symlink = "none";
+          }
+        ];
 
-        contents = [{
-          source = config.system.build.toplevel + "/init";
-          target = "/sbin/init";
-        }];
+        contents = [
+          {
+            source = config.system.build.toplevel + "/init";
+            target = "/sbin/init";
+          }
+        ];
 
         extraCommands = "mkdir -p root etc/systemd/network";
       };
 
+      boot.postBootCommands = ''
+        # After booting, register the contents of the Nix store in the Nix
+        # database.
+        if [ -f /nix-path-registration ]; then
+          ${config.nix.package.out}/bin/nix-store --load-db < /nix-path-registration &&
+          rm /nix-path-registration
+        fi
+
+        # nixos-rebuild also requires a "system" profile
+        ${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
+      '';
+
       boot = {
         isContainer = true;
         loader.initScript.enable = true;
@@ -65,17 +91,36 @@ with lib;
         hostName = mkIf (!cfg.manageHostName) (mkForce "");
       };
 
+      # unprivileged LXCs can't set net.ipv4.ping_group_range
+      security.wrappers.ping = mkIf (!cfg.privileged) {
+        owner = "root";
+        group = "root";
+        capabilities = "cap_net_raw+p";
+        source = "${pkgs.iputils.out}/bin/ping";
+      };
+
       services.openssh = {
         enable = mkDefault true;
         startWhenNeeded = mkDefault true;
       };
 
       systemd = {
-        mounts = mkIf (!cfg.privileged) [{
-          enable = false;
-          where = "/sys/kernel/debug";
-        }];
-        services."getty@".unitConfig.ConditionPathExists = [ "" "/dev/%I" ];
+        mounts = mkIf (!cfg.privileged) [
+          {
+            enable = false;
+            where = "/sys/kernel/debug";
+          }
+        ];
+
+        # By default only starts getty on tty0 but first on LXC is tty1
+        services."autovt@".unitConfig.ConditionPathExists = [
+          ""
+          "/dev/%I"
+        ];
+
+        # These are disabled by `console.enable` but console via tty is the default in Proxmox
+        services."getty@tty1".enable = lib.mkForce true;
+        services."autovt@".enable = lib.mkForce true;
       };
 
     };
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index d1dc6404d4f51..10f69f3a744ad 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -134,32 +134,25 @@ let
           TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir)
       fi
 
-      ${lib.optionalString (cfg.useNixStoreImage)
-        (if cfg.writableStore
-          then ''
-            # Create a writable copy/snapshot of the store image.
-            ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${storeImage}/nixos.qcow2 "$TMPDIR"/store.img
-          ''
-          else ''
-            (
-              cd ${builtins.storeDir}
-              ${hostPkgs.erofs-utils}/bin/mkfs.erofs \
-                --force-uid=0 \
-                --force-gid=0 \
-                -L ${nixStoreFilesystemLabel} \
-                -U eb176051-bd15-49b7-9e6b-462e0b467019 \
-                -T 0 \
-                --exclude-regex="$(
-                  <${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \
-                    sed -e 's^.*/^^g' \
-                  | cut -c -10 \
-                  | ${hostPkgs.python3}/bin/python ${./includes-to-excludes.py} )" \
-                "$TMPDIR"/store.img \
-                . \
-                </dev/null >/dev/null
-            )
-          ''
-        )
+      ${lib.optionalString (cfg.useNixStoreImage) ''
+        echo "Creating Nix store image..."
+
+        ${hostPkgs.gnutar}/bin/tar --create \
+          --absolute-names \
+          --verbatim-files-from \
+          --transform 'flags=rSh;s|/nix/store/||' \
+          --files-from ${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \
+          | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \
+            --force-uid=0 \
+            --force-gid=0 \
+            -L ${nixStoreFilesystemLabel} \
+            -U eb176051-bd15-49b7-9e6b-462e0b467019 \
+            -T 0 \
+            --tar=f \
+            "$TMPDIR"/store.img
+
+        echo "Created Nix store image."
+      ''
       }
 
       # Create a directory for exchanging data with the VM.
@@ -298,21 +291,6 @@ let
     OVMF = cfg.efi.OVMF;
   };
 
-  storeImage = import ../../lib/make-disk-image.nix {
-    name = "nix-store-image";
-    inherit pkgs config lib;
-    additionalPaths = [ regInfo ];
-    format = "qcow2";
-    onlyNixStore = true;
-    label = nixStoreFilesystemLabel;
-    partitionTableType = "none";
-    installBootLoader = false;
-    touchEFIVars = false;
-    diskSize = "auto";
-    additionalSpace = "0M";
-    copyChannel = false;
-  };
-
 in
 
 {
@@ -393,7 +371,7 @@ in
             The path (inside the VM) to the device containing the EFI System Partition (ESP).
 
             If you are *not* booting from a UEFI firmware, this value is, by
-            default, `null`. The ESP is mounted under `/boot`.
+            default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`.
           '';
       };
 
@@ -665,6 +643,14 @@ in
         description = "Primary IP address used in /etc/hosts.";
       };
 
+    networking.primaryIPv6Address =
+      mkOption {
+        type = types.str;
+        default = "";
+        internal = true;
+        description = "Primary IPv6 address used in /etc/hosts.";
+      };
+
     virtualisation.host.pkgs = mkOption {
       type = options.nixpkgs.pkgs.type;
       default = pkgs;
@@ -780,10 +766,14 @@ in
           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`.
+          The Nix store image is built just-in-time right before the VM is
+          started. Because it does not produce another derivation, the image is
+          not cached between invocations and never lands in the store or binary
+          cache.
+
+          If you want a full disk image with a partition table and a root
+          filesystem instead of only a store image, enable
+          {option}`virtualisation.useBootLoader` instead.
         '';
       };
 
@@ -1011,25 +1001,7 @@ in
         ];
 
     warnings =
-      optional (
-        cfg.writableStore &&
-        cfg.useNixStoreImage &&
-        opt.writableStore.highestPrio > lib.modules.defaultOverridePriority)
-        ''
-          You have enabled ${opt.useNixStoreImage} = true,
-          without setting ${opt.writableStore} = false.
-
-          This causes a store image to be written to the store, which is
-          costly, especially for the binary cache, and because of the need
-          for more frequent garbage collection.
-
-          If you really need this combination, you can set ${opt.writableStore}
-          explicitly to true, incur the cost and make this warning go away.
-          Otherwise, we recommend
-
-            ${opt.writableStore} = false;
-            ''
-      ++ optional (cfg.directBoot.enable && cfg.useBootLoader)
+      optional (cfg.directBoot.enable && cfg.useBootLoader)
         ''
           You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering
           `useBootLoader` useless. You might want to disable one of those options.
@@ -1042,42 +1014,8 @@ in
     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}";
 
-    boot.initrd.kernelModules = optionals (cfg.useNixStoreImage && !cfg.writableStore) [ "erofs" ];
-
     boot.loader.supportsInitrdSecrets = mkIf (!cfg.useBootLoader) (mkVMOverride false);
 
-    boot.initrd.postMountCommands = lib.mkIf (!config.boot.initrd.systemd.enable)
-      ''
-        # Mark this as a NixOS machine.
-        mkdir -p $targetRoot/etc
-        echo -n > $targetRoot/etc/NIXOS
-
-        # Fix the permissions on /tmp.
-        chmod 1777 $targetRoot/tmp
-
-        mkdir -p $targetRoot/boot
-
-        ${optionalString cfg.writableStore ''
-          echo "mounting overlay filesystem on /nix/store..."
-          mkdir -p -m 0755 $targetRoot/nix/.rw-store/store $targetRoot/nix/.rw-store/work $targetRoot/nix/store
-          mount -t overlay overlay $targetRoot/nix/store \
-            -o lowerdir=$targetRoot/nix/.ro-store,upperdir=$targetRoot/nix/.rw-store/store,workdir=$targetRoot/nix/.rw-store/work || fail
-        ''}
-      '';
-
-    systemd.tmpfiles.settings."10-qemu-vm" = lib.mkIf config.boot.initrd.systemd.enable {
-      "/etc/NIXOS".f = {
-        mode = "0644";
-        user = "root";
-        group = "root";
-      };
-      "${config.boot.loader.efi.efiSysMountPoint}".d = {
-        mode = "0644";
-        user = "root";
-        group = "root";
-      };
-    };
-
     # After booting, register the closure of the paths in
     # `virtualisation.additionalPaths' in the Nix database in the VM.  This
     # allows Nix operations to work in the VM.  The path to the
@@ -1093,8 +1031,7 @@ in
       '';
 
     boot.initrd.availableKernelModules =
-      optional cfg.writableStore "overlay"
-      ++ optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx"
+      optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx"
       ++ optional (cfg.tpm.enable) "tpm_tis";
 
     virtualisation.additionalPaths = [ config.system.build.toplevel ];
@@ -1102,7 +1039,9 @@ in
     virtualisation.sharedDirectories = {
       nix-store = mkIf cfg.mountHostNixStore {
         source = builtins.storeDir;
-        target = "/nix/store";
+        # Always mount this to /nix/.ro-store because we never want to actually
+        # write to the host Nix Store.
+        target = "/nix/.ro-store";
         securityModel = "none";
       };
       xchg = {
@@ -1194,7 +1133,7 @@ in
         name = "nix-store";
         file = ''"$TMPDIR"/store.img'';
         deviceExtraOpts.bootindex = "2";
-        driveExtraOpts.format = if cfg.writableStore then "qcow2" else "raw";
+        driveExtraOpts.format = "raw";
       }])
       (imap0 (idx: _: {
         file = "$(pwd)/empty${toString idx}.qcow2";
@@ -1212,10 +1151,7 @@ in
     virtualisation.fileSystems = let
       mkSharedDir = tag: share:
         {
-          name =
-            if tag == "nix-store" && cfg.writableStore
-            then "/nix/.ro-store"
-            else share.target;
+          name = share.target;
           value.device = tag;
           value.fsType = "9p";
           value.neededForBoot = true;
@@ -1240,8 +1176,19 @@ in
           # Sync with systemd's tmp.mount;
           options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ];
         };
-        "/nix/${if cfg.writableStore then ".ro-store" else "store"}" = lib.mkIf cfg.useNixStoreImage {
+        "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (if cfg.writableStore then {
+          overlay = {
+            lowerdir = [ "/nix/.ro-store" ];
+            upperdir = "/nix/.rw-store/upper";
+            workdir = "/nix/.rw-store/work";
+          };
+        } else {
+          device = "/nix/.ro-store";
+          options = [ "bind" ];
+        });
+        "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage {
           device = "/dev/disk/by-label/${nixStoreFilesystemLabel}";
+          fsType = "erofs";
           neededForBoot = true;
           options = [ "ro" ];
         };
@@ -1250,40 +1197,13 @@ in
           options = [ "mode=0755" ];
           neededForBoot = true;
         };
-        "/boot" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) {
+        "${config.boot.loader.efi.efiSysMountPoint}" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) {
           device = cfg.bootPartition;
           fsType = "vfat";
-          noCheck = true; # fsck fails on a r/o filesystem
         };
       }
     ];
 
-    boot.initrd.systemd = lib.mkIf (config.boot.initrd.systemd.enable && cfg.writableStore) {
-      mounts = [{
-        where = "/sysroot/nix/store";
-        what = "overlay";
-        type = "overlay";
-        options = "lowerdir=/sysroot/nix/.ro-store,upperdir=/sysroot/nix/.rw-store/store,workdir=/sysroot/nix/.rw-store/work";
-        wantedBy = ["initrd-fs.target"];
-        before = ["initrd-fs.target"];
-        requires = ["rw-store.service"];
-        after = ["rw-store.service"];
-        unitConfig.RequiresMountsFor = "/sysroot/nix/.ro-store";
-      }];
-      services.rw-store = {
-        before = [ "shutdown.target" ];
-        conflicts = [ "shutdown.target" ];
-        unitConfig = {
-          DefaultDependencies = false;
-          RequiresMountsFor = "/sysroot/nix/.rw-store";
-        };
-        serviceConfig = {
-          Type = "oneshot";
-          ExecStart = "/bin/mkdir -p -m 0755 /sysroot/nix/.rw-store/store /sysroot/nix/.rw-store/work /sysroot/nix/store";
-        };
-      };
-    };
-
     swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ];
     boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {};
 
diff --git a/nixos/modules/virtualisation/vagrant-virtualbox-image.nix b/nixos/modules/virtualisation/vagrant-virtualbox-image.nix
index 2a921894ab612..556228436b992 100644
--- a/nixos/modules/virtualisation/vagrant-virtualbox-image.nix
+++ b/nixos/modules/virtualisation/vagrant-virtualbox-image.nix
@@ -15,7 +15,6 @@
     usb = "off";
     usbehci = "off";
   };
-  sound.enable = false;
   documentation.man.enable = false;
   documentation.nixos.enable = false;
 
diff --git a/nixos/release-small.nix b/nixos/release-small.nix
index 98e36b0669e79..d4d7b7b4149b4 100644
--- a/nixos/release-small.nix
+++ b/nixos/release-small.nix
@@ -32,7 +32,7 @@ let
 in rec {
 
   nixos = {
-    inherit (nixos') channel manual options iso_minimal amazonImage dummy;
+    inherit (nixos') channel manual options iso_minimal dummy;
     tests = {
       inherit (nixos'.tests)
         acme
@@ -78,6 +78,7 @@ in rec {
       nginx
       nodejs
       openssh
+      opensshTest
       php
       postgresql
       python
@@ -115,7 +116,6 @@ in rec {
       (map onSupported [
         "nixos.dummy"
         "nixos.iso_minimal"
-        "nixos.amazonImage"
         "nixos.manual"
         "nixos.tests.acme"
         "nixos.tests.boot.uefiCdrom"
@@ -139,6 +139,7 @@ in rec {
         "nixos.tests.simple"
         "nixpkgs.jdk"
         "nixpkgs.tests-stdenv-gcc-stageCompare"
+        "nixpkgs.opensshTest"
       ])
     ];
   };
diff --git a/nixos/release.nix b/nixos/release.nix
index 2f31973569bf9..eeca73ea4c550 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -318,6 +318,101 @@ in rec {
 
   );
 
+  # An image that can be imported into incus and used for container creation
+  incusContainerImage =
+    forMatchingSystems
+      [
+        "x86_64-linux"
+        "aarch64-linux"
+      ]
+      (
+        system:
+        with import ./.. { inherit system; };
+
+        hydraJob (
+          (import lib/eval-config.nix {
+            inherit system;
+            modules = [
+              configuration
+              versionModule
+              ./maintainers/scripts/incus/incus-container-image.nix
+            ];
+          }).config.system.build.squashfs
+        )
+      );
+
+  # Metadata for the incus image
+  incusContainerMeta =
+    forMatchingSystems
+      [
+        "x86_64-linux"
+        "aarch64-linux"
+      ]
+      (
+        system:
+
+        with import ./.. { inherit system; };
+
+        hydraJob (
+          (import lib/eval-config.nix {
+            inherit system;
+            modules = [
+              configuration
+              versionModule
+              ./maintainers/scripts/incus/incus-container-image.nix
+            ];
+          }).config.system.build.metadata
+        )
+      );
+
+  # An image that can be imported into incus and used for container creation
+  incusVirtualMachineImage =
+    forMatchingSystems
+      [
+        "x86_64-linux"
+        "aarch64-linux"
+      ]
+      (
+        system:
+
+        with import ./.. { inherit system; };
+
+        hydraJob (
+          (import lib/eval-config.nix {
+            inherit system;
+            modules = [
+              configuration
+              versionModule
+              ./maintainers/scripts/incus/incus-virtual-machine-image.nix
+            ];
+          }).config.system.build.qemuImage
+        )
+      );
+
+  # Metadata for the incus image
+  incusVirtualMachineImageMeta =
+    forMatchingSystems
+      [
+        "x86_64-linux"
+        "aarch64-linux"
+      ]
+      (
+        system:
+
+        with import ./.. { inherit system; };
+
+        hydraJob (
+          (import lib/eval-config.nix {
+            inherit system;
+            modules = [
+              configuration
+              versionModule
+              ./maintainers/scripts/incus/incus-virtual-machine-image.nix
+            ];
+          }).config.system.build.metadata
+        )
+      );
+
   # An image that can be imported into lxd and used for container creation
   lxdContainerImage = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
 
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
index 2cba04f9d3957..a4f00be887be2 100644
--- a/nixos/tests/acme.nix
+++ b/nixos/tests/acme.nix
@@ -124,7 +124,7 @@
     };
 
     # Test that server reloads when an alias is removed (and subsequently test removal works in acme)
-    "${server}-remove-alias".configuration = { nodes, config, ... }: baseConfig {
+    "${server}_remove_alias".configuration = { nodes, config, ... }: baseConfig {
       inherit nodes config;
       specialConfig = {
         # Remove an alias, but create a standalone vhost in its place for testing.
@@ -140,7 +140,7 @@
     };
 
     # Test that the server reloads when only the acme configuration is changed.
-    "${server}-change-acme-conf".configuration = { nodes, config, ... }: baseConfig {
+    "${server}_change_acme_conf".configuration = { nodes, config, ... }: baseConfig {
       inherit nodes config;
       specialConfig = {
         security.acme.certs."${server}-http.example.test" = {
@@ -251,7 +251,7 @@ in {
         ];
 
         # Test OCSP Stapling
-        ocsp-stapling.configuration = { ... }: lib.mkMerge [
+        ocsp_stapling.configuration = { ... }: lib.mkMerge [
           webserverBasicConfig
           {
             security.acme.certs."a.example.test".ocspMustStaple = true;
@@ -266,7 +266,7 @@ in {
 
         # Validate service relationships by adding a slow start service to nginx' wants.
         # Reproducer for https://github.com/NixOS/nixpkgs/issues/81842
-        slow-startup.configuration = { ... }: lib.mkMerge [
+        slow_startup.configuration = { ... }: lib.mkMerge [
           webserverBasicConfig
           {
             systemd.services.my-slow-service = {
@@ -284,7 +284,7 @@ in {
           }
         ];
 
-        concurrency-limit.configuration = {pkgs, ...}: lib.mkMerge [
+        concurrency_limit.configuration = {pkgs, ...}: lib.mkMerge [
           webserverBasicConfig {
             security.acme.maxConcurrentRenewals = 1;
 
@@ -317,7 +317,7 @@ in {
 
         # Test lego internal server (listenHTTP option)
         # Also tests useRoot option
-        lego-server.configuration = { ... }: {
+        lego_server.configuration = { ... }: {
           security.acme.useRoot = true;
           security.acme.certs."lego.example.test" = {
             listenHTTP = ":80";
@@ -358,7 +358,7 @@ in {
         caddy.configuration = baseCaddyConfig;
 
         # Test that the server reloads when only the acme configuration is changed.
-        "caddy-change-acme-conf".configuration = { nodes, config, ... }: lib.mkMerge [
+        "caddy_change_acme_conf".configuration = { nodes, config, ... }: lib.mkMerge [
           (baseCaddyConfig {
             inherit nodes config;
           })
@@ -629,12 +629,12 @@ in {
           webserver.succeed("systemctl start nginx-config-reload.service")
 
       with subtest("Correctly implements OCSP stapling"):
-          switch_to(webserver, "ocsp-stapling")
+          switch_to(webserver, "ocsp_stapling")
           webserver.wait_for_unit("acme-finished-a.example.test.target")
           check_stapling(client, "a.example.test")
 
       with subtest("Can request certificate with HTTP-01 using lego's internal web server"):
-          switch_to(webserver, "lego-server")
+          switch_to(webserver, "lego_server")
           webserver.wait_for_unit("acme-finished-lego.example.test.target")
           webserver.wait_for_unit("nginx.service")
           webserver.succeed("echo HENLO && systemctl cat nginx.service")
@@ -644,14 +644,14 @@ in {
 
       with subtest("Can request certificate with HTTP-01 when nginx startup is delayed"):
           webserver.execute("systemctl stop nginx")
-          switch_to(webserver, "slow-startup")
+          switch_to(webserver, "slow_startup")
           webserver.wait_for_unit("acme-finished-slow.example.test.target")
           check_issuer(webserver, "slow.example.test", "pebble")
           webserver.wait_for_unit("nginx.service")
           check_connection(client, "slow.example.test")
 
       with subtest("Can limit concurrency of running renewals"):
-          switch_to(webserver, "concurrency-limit")
+          switch_to(webserver, "concurrency_limit")
           webserver.wait_for_unit("acme-finished-f.example.test.target")
           webserver.wait_for_unit("acme-finished-g.example.test.target")
           webserver.wait_for_unit("acme-finished-h.example.test.target")
@@ -669,7 +669,7 @@ in {
           check_connection(client, "a.example.test")
 
       with subtest("security.acme changes reflect on caddy"):
-          switch_to(webserver, "caddy-change-acme-conf")
+          switch_to(webserver, "caddy_change_acme_conf")
           webserver.wait_for_unit("acme-finished-example.test.target")
           webserver.wait_for_unit("caddy.service")
           # FIXME reloading caddy is not sufficient to load new certs.
@@ -721,7 +721,7 @@ in {
 
           with subtest("Can remove an alias from a domain + cert is updated"):
               test_alias = f"{server}-{domains[0]}-alias.example.test"
-              switch_to(webserver, f"{server}-remove-alias")
+              switch_to(webserver, f"{server}_remove_alias")
               webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
               wait_for_server()
               check_connection(client, test_domain)
@@ -736,7 +736,7 @@ in {
               # Switch back to normal server config first, reset everything.
               switch_to(webserver, server)
               wait_for_server()
-              switch_to(webserver, f"{server}-change-acme-conf")
+              switch_to(webserver, f"{server}_change_acme_conf")
               webserver.wait_for_unit(f"acme-finished-{test_domain}.target")
               wait_for_server()
               check_connection_key_bits(client, test_domain, "384")
diff --git a/nixos/tests/activation/etc-overlay-immutable.nix b/nixos/tests/activation/etc-overlay-immutable.nix
index f0abf70d350ff..6d56db43f0b25 100644
--- a/nixos/tests/activation/etc-overlay-immutable.nix
+++ b/nixos/tests/activation/etc-overlay-immutable.nix
@@ -15,6 +15,11 @@
     boot.kernelPackages = pkgs.linuxPackages_latest;
     time.timeZone = "Utc";
 
+    environment.etc = {
+      "mountpoint/.keep".text = "keep";
+      "filemount".text = "keep";
+    };
+
     specialisation.new-generation.configuration = {
       environment.etc."newgen".text = "newgen";
     };
@@ -27,14 +32,38 @@
     with subtest("direct symlinks point to the target without indirection"):
       assert machine.succeed("readlink -n /etc/localtime") == "/etc/zoneinfo/Utc"
 
+    with subtest("/etc/mtab points to the right file"):
+      assert "/proc/mounts" == machine.succeed("readlink --no-newline /etc/mtab")
+
+    with subtest("Correct mode on the source password files"):
+      assert machine.succeed("stat -c '%a' /var/lib/nixos/etc/passwd") == "644\n"
+      assert machine.succeed("stat -c '%a' /var/lib/nixos/etc/group") == "644\n"
+      assert machine.succeed("stat -c '%a' /var/lib/nixos/etc/shadow") == "0\n"
+      assert machine.succeed("stat -c '%a' /var/lib/nixos/etc/gshadow") == "0\n"
+
+    with subtest("Password files are symlinks to /var/lib/nixos/etc"):
+      assert machine.succeed("readlink -f /etc/passwd") == "/var/lib/nixos/etc/passwd\n"
+      assert machine.succeed("readlink -f /etc/group") == "/var/lib/nixos/etc/group\n"
+      assert machine.succeed("readlink -f /etc/shadow") == "/var/lib/nixos/etc/shadow\n"
+      assert machine.succeed("readlink -f /etc/gshadow") == "/var/lib/nixos/etc/gshadow\n"
+
     with subtest("switching to the same generation"):
       machine.succeed("/run/current-system/bin/switch-to-configuration test")
 
     with subtest("switching to a new generation"):
       machine.fail("stat /etc/newgen")
 
+      machine.succeed("mount -t tmpfs tmpfs /etc/mountpoint")
+      machine.succeed("touch /etc/mountpoint/extra-file")
+      machine.succeed("mount --bind /dev/null /etc/filemount")
+
       machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
 
       assert machine.succeed("cat /etc/newgen") == "newgen"
+
+      print(machine.succeed("findmnt /etc/mountpoint"))
+      print(machine.succeed("ls /etc/mountpoint"))
+      print(machine.succeed("stat /etc/mountpoint/extra-file"))
+      print(machine.succeed("findmnt /etc/filemount"))
   '';
 }
diff --git a/nixos/tests/activation/etc-overlay-mutable.nix b/nixos/tests/activation/etc-overlay-mutable.nix
index 087c06408a715..8561ff7fd230d 100644
--- a/nixos/tests/activation/etc-overlay-mutable.nix
+++ b/nixos/tests/activation/etc-overlay-mutable.nix
@@ -28,9 +28,22 @@
       machine.fail("stat /etc/newgen")
       machine.succeed("echo -n 'mutable' > /etc/mutable")
 
+      # Directory
+      machine.succeed("mkdir /etc/mountpoint")
+      machine.succeed("mount -t tmpfs tmpfs /etc/mountpoint")
+      machine.succeed("touch /etc/mountpoint/extra-file")
+
+      # File
+      machine.succeed("touch /etc/filemount")
+      machine.succeed("mount --bind /dev/null /etc/filemount")
+
       machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
 
       assert machine.succeed("cat /etc/newgen") == "newgen"
       assert machine.succeed("cat /etc/mutable") == "mutable"
+
+      print(machine.succeed("findmnt /etc/mountpoint"))
+      print(machine.succeed("stat /etc/mountpoint/extra-file"))
+      print(machine.succeed("findmnt /etc/filemount"))
   '';
 }
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 3989ab5956fa1..424ccf4d9a44f 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -160,6 +160,7 @@ in {
   bootspec = handleTestOn ["x86_64-linux"] ./bootspec.nix {};
   boot-stage1 = handleTest ./boot-stage1.nix {};
   borgbackup = handleTest ./borgbackup.nix {};
+  borgmatic = handleTest ./borgmatic.nix {};
   botamusique = handleTest ./botamusique.nix {};
   bpf = handleTestOn ["x86_64-linux" "aarch64-linux"] ./bpf.nix {};
   bpftune = handleTest ./bpftune.nix {};
@@ -235,15 +236,18 @@ in {
   couchdb = handleTest ./couchdb.nix {};
   crabfit = handleTest ./crabfit.nix {};
   cri-o = handleTestOn ["aarch64-linux" "x86_64-linux"] ./cri-o.nix {};
+  cryptpad = runTest ./cryptpad.nix;
   cups-pdf = handleTest ./cups-pdf.nix {};
   curl-impersonate = handleTest ./curl-impersonate.nix {};
   custom-ca = handleTest ./custom-ca.nix {};
   croc = handleTest ./croc.nix {};
   darling = handleTest ./darling.nix {};
+  darling-dmg = runTest ./darling-dmg.nix;
   dae = handleTest ./dae.nix {};
   davis = handleTest ./davis.nix {};
   db-rest = handleTest ./db-rest.nix {};
   dconf = handleTest ./dconf.nix {};
+  ddns-updater = handleTest ./ddns-updater.nix {};
   deconz = handleTest ./deconz.nix {};
   deepin = handleTest ./deepin.nix {};
   deluge = handleTest ./deluge.nix {};
@@ -261,6 +265,7 @@ in {
   docker-rootless = handleTestOn ["aarch64-linux" "x86_64-linux"] ./docker-rootless.nix {};
   docker-registry = handleTest ./docker-registry.nix {};
   docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {};
+  docker-tools-nix-shell = runTest ./docker-tools-nix-shell.nix;
   docker-tools-cross = handleTestOn ["x86_64-linux" "aarch64-linux"] ./docker-tools-cross.nix {};
   docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {};
   documize = handleTest ./documize.nix {};
@@ -272,6 +277,7 @@ in {
   dovecot = handleTest ./dovecot.nix {};
   drawterm = discoverTests (import ./drawterm.nix);
   drbd = handleTest ./drbd.nix {};
+  druid = handleTestOn [ "x86_64-linux" ] ./druid {};
   dublin-traceroute = handleTest ./dublin-traceroute.nix {};
   earlyoom = handleTestOn ["x86_64-linux"] ./earlyoom.nix {};
   early-mount-options = handleTest ./early-mount-options.nix {};
@@ -280,6 +286,7 @@ in {
   ecryptfs = handleTest ./ecryptfs.nix {};
   fscrypt = handleTest ./fscrypt.nix {};
   fastnetmon-advanced = runTest ./fastnetmon-advanced.nix;
+  eintopf = handleTest ./eintopf.nix {};
   ejabberd = handleTest ./xmpp/ejabberd.nix {};
   elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
   emacs-daemon = handleTest ./emacs-daemon.nix {};
@@ -296,6 +303,7 @@ in {
   esphome = handleTest ./esphome.nix {};
   etc = pkgs.callPackage ../modules/system/etc/test.nix { inherit evalMinimalConfig; };
   activation = pkgs.callPackage ../modules/system/activation/test.nix { };
+  activation-lib = pkgs.callPackage ../modules/system/activation/lib/test.nix { };
   activation-var = runTest ./activation/var.nix;
   activation-nix-channel = runTest ./activation/nix-channel.nix;
   activation-etc-overlay-mutable = runTest ./activation/etc-overlay-mutable.nix;
@@ -322,18 +330,21 @@ in {
   firefox-devedition = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-devedition; };
   firefox-esr    = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr; }; # used in `tested` job
   firefox-esr-115 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-115; };
+  firefox-esr-128 = handleTest ./firefox.nix { firefoxPackage = pkgs.firefox-esr-128; };
   firefoxpwa = handleTest ./firefoxpwa.nix {};
   firejail = handleTest ./firejail.nix {};
   firewall = handleTest ./firewall.nix { nftables = false; };
   firewall-nftables = handleTest ./firewall.nix { nftables = true; };
   fish = handleTest ./fish.nix {};
   flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
+  flaresolverr = handleTest ./flaresolverr.nix {};
   flood = handleTest ./flood.nix {};
   floorp = handleTest ./firefox.nix { firefoxPackage = pkgs.floorp; };
   fluentd = handleTest ./fluentd.nix {};
   fluidd = handleTest ./fluidd.nix {};
   fontconfig-default-fonts = handleTest ./fontconfig-default-fonts.nix {};
-  forgejo = handleTest ./forgejo.nix { };
+  forgejo = handleTest ./forgejo.nix { forgejoPackage = pkgs.forgejo; };
+  forgejo-lts = handleTest ./forgejo.nix { forgejoPackage = pkgs.forgejo-lts; };
   freenet = handleTest ./freenet.nix {};
   freeswitch = handleTest ./freeswitch.nix {};
   freetube = discoverTests (import ./freetube.nix);
@@ -362,6 +373,7 @@ in {
   gitlab = runTest ./gitlab.nix;
   gitolite = handleTest ./gitolite.nix {};
   gitolite-fcgiwrap = handleTest ./gitolite-fcgiwrap.nix {};
+  glance = runTest ./glance.nix;
   glusterfs = handleTest ./glusterfs.nix {};
   gnome = handleTest ./gnome.nix {};
   gnome-extensions = handleTest ./gnome-extensions.nix {};
@@ -369,6 +381,7 @@ in {
   gnome-xorg = handleTest ./gnome-xorg.nix {};
   gns3-server = handleTest ./gns3-server.nix {};
   gnupg = handleTest ./gnupg.nix {};
+  goatcounter = handleTest ./goatcounter.nix {};
   go-neb = handleTest ./go-neb.nix {};
   gobgpd = handleTest ./gobgpd.nix {};
   gocd-agent = handleTest ./gocd-agent.nix {};
@@ -377,6 +390,7 @@ in {
   gonic = handleTest ./gonic.nix {};
   google-oslogin = handleTest ./google-oslogin {};
   goss = handleTest ./goss.nix {};
+  gotenberg = handleTest ./gotenberg.nix {};
   gotify-server = handleTest ./gotify-server.nix {};
   gotosocial = runTest ./web-apps/gotosocial.nix;
   grafana = handleTest ./grafana {};
@@ -391,7 +405,7 @@ in {
   guix = handleTest ./guix {};
   gvisor = handleTest ./gvisor.nix {};
   hadoop = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop; };
-  hadoop_3_2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop_3_2; };
+  hadoop_3_3 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop_3_3; };
   hadoop2 = import ./hadoop { inherit handleTestOn; package=pkgs.hadoop2; };
   haka = handleTest ./haka.nix {};
   haste-server = handleTest ./haste-server.nix {};
@@ -401,6 +415,7 @@ in {
   headscale = handleTest ./headscale.nix {};
   healthchecks = handleTest ./web-apps/healthchecks.nix {};
   hbase2 = handleTest ./hbase.nix { package=pkgs.hbase2; };
+  hbase_2_5 = handleTest ./hbase.nix { package=pkgs.hbase_2_5; };
   hbase_2_4 = handleTest ./hbase.nix { package=pkgs.hbase_2_4; };
   hbase3 = handleTest ./hbase.nix { package=pkgs.hbase3; };
   hddfancontrol = handleTest ./hddfancontrol.nix {};
@@ -433,6 +448,7 @@ in {
   hydra = handleTest ./hydra {};
   i3wm = handleTest ./i3wm.nix {};
   icingaweb2 = handleTest ./icingaweb2.nix {};
+  ifm = handleTest ./ifm.nix {};
   iftop = handleTest ./iftop.nix {};
   incron = handleTest ./incron.nix {};
   incus = pkgs.recurseIntoAttrs (handleTest ./incus { inherit handleTestOn; inherit (pkgs) incus; });
@@ -498,7 +514,6 @@ in {
   leaps = handleTest ./leaps.nix {};
   lemmy = handleTest ./lemmy.nix {};
   libinput = handleTest ./libinput.nix {};
-  libreddit = handleTest ./libreddit.nix {};
   librenms = handleTest ./librenms.nix {};
   libresprite = handleTest ./libresprite.nix {};
   libreswan = runTest ./libreswan.nix;
@@ -513,6 +528,7 @@ in {
   listmonk = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./listmonk.nix {};
   litestream = handleTest ./litestream.nix {};
   lldap = handleTest ./lldap.nix {};
+  localsend = handleTest ./localsend.nix {};
   locate = handleTest ./locate.nix {};
   login = handleTest ./login.nix {};
   logrotate = handleTest ./logrotate.nix {};
@@ -522,10 +538,14 @@ in {
   lxd = pkgs.recurseIntoAttrs (handleTest ./lxd { inherit handleTestOn; });
   lxd-image-server = handleTest ./lxd-image-server.nix {};
   #logstash = handleTest ./logstash.nix {};
-  lomiri = handleTest ./lomiri.nix {};
+  lomiri = discoverTests (import ./lomiri.nix);
+  lomiri-calculator-app = runTest ./lomiri-calculator-app.nix;
+  lomiri-camera-app = runTest ./lomiri-camera-app.nix;
+  lomiri-clock-app = runTest ./lomiri-clock-app.nix;
   lomiri-filemanager-app = runTest ./lomiri-filemanager-app.nix;
   lomiri-system-settings = handleTest ./lomiri-system-settings.nix {};
   lorri = handleTest ./lorri/default.nix {};
+  ly = handleTest ./ly.nix {};
   maddy = discoverTests (import ./maddy { inherit handleTest; });
   maestral = handleTest ./maestral.nix {};
   magic-wormhole-mailbox-server = handleTest ./magic-wormhole-mailbox-server.nix {};
@@ -535,7 +555,7 @@ in {
   mailman = handleTest ./mailman.nix {};
   man = handleTest ./man.nix {};
   mariadb-galera = handleTest ./mysql/mariadb-galera.nix {};
-  mastodon = discoverTests (import ./web-apps/mastodon { inherit handleTestOn; });
+  mastodon = pkgs.recurseIntoAttrs (handleTest ./web-apps/mastodon { inherit handleTestOn; });
   pixelfed = discoverTests (import ./web-apps/pixelfed { inherit handleTestOn; });
   mate = handleTest ./mate.nix {};
   mate-wayland = handleTest ./mate-wayland.nix {};
@@ -563,8 +583,10 @@ in {
   minidlna = handleTest ./minidlna.nix {};
   miniflux = handleTest ./miniflux.nix {};
   minio = handleTest ./minio.nix {};
+  miracle-wm = runTest ./miracle-wm.nix;
   miriway = handleTest ./miriway.nix {};
   misc = handleTest ./misc.nix {};
+  misskey = handleTest ./misskey.nix {};
   mjolnir = handleTest ./matrix/mjolnir.nix {};
   mobilizon = handleTest ./mobilizon.nix {};
   mod_perl = handleTest ./mod_perl.nix {};
@@ -589,9 +611,9 @@ in {
   # Fails on aarch64-linux at the PDF creation step - need to debug this on an
   # aarch64 machine..
   musescore = handleTestOn ["x86_64-linux"] ./musescore.nix {};
+  music-assistant = runTest ./music-assistant.nix;
   munin = handleTest ./munin.nix {};
   mutableUsers = handleTest ./mutable-users.nix {};
-  mxisd = handleTest ./mxisd.nix {};
   mycelium = handleTest ./mycelium {};
   mympd = handleTest ./mympd.nix {};
   mysql = handleTest ./mysql/mysql.nix {};
@@ -610,6 +632,7 @@ in {
   nbd = handleTest ./nbd.nix {};
   ncdns = handleTest ./ncdns.nix {};
   ndppd = handleTest ./ndppd.nix {};
+  nix-channel = pkgs.callPackage ../modules/config/nix-channel/test.nix { };
   nebula = handleTest ./nebula.nix {};
   netbird = handleTest ./netbird.nix {};
   nimdow = handleTest ./nimdow.nix {};
@@ -681,7 +704,9 @@ in {
   ocis = handleTest ./ocis.nix {};
   oddjobd = handleTestOn [ "x86_64-linux" "aarch64-linux" ] ./oddjobd.nix {};
   oh-my-zsh = handleTest ./oh-my-zsh.nix {};
-  ollama = handleTest ./ollama.nix {};
+  ollama = runTest ./ollama.nix;
+  ollama-cuda = runTestOn ["x86_64-linux" "aarch64-linux"] ./ollama-cuda.nix;
+  ollama-rocm = runTestOn ["x86_64-linux" "aarch64-linux"] ./ollama-rocm.nix;
   ombi = handleTest ./ombi.nix {};
   openarena = handleTest ./openarena.nix {};
   openldap = handleTest ./openldap.nix {};
@@ -766,6 +791,7 @@ in {
   postgresql = handleTest ./postgresql.nix {};
   postgresql-jit = handleTest ./postgresql-jit.nix {};
   postgresql-wal-receiver = handleTest ./postgresql-wal-receiver.nix {};
+  postgresql-tls-client-cert = handleTest ./postgresql-tls-client-cert.nix {};
   powerdns = handleTest ./powerdns.nix {};
   powerdns-admin = handleTest ./powerdns-admin.nix {};
   power-profiles-daemon = handleTest ./power-profiles-daemon.nix {};
@@ -792,6 +818,7 @@ in {
   qemu-vm-restrictnetwork = handleTest ./qemu-vm-restrictnetwork.nix {};
   qemu-vm-volatile-root = runTest ./qemu-vm-volatile-root.nix;
   qemu-vm-external-disk-image = runTest ./qemu-vm-external-disk-image.nix;
+  qemu-vm-store = runTest ./qemu-vm-store.nix;
   qgis = handleTest ./qgis.nix { qgisPackage = pkgs.qgis; };
   qgis-ltr = handleTest ./qgis.nix { qgisPackage = pkgs.qgis-ltr; };
   qownnotes = handleTest ./qownnotes.nix {};
@@ -803,9 +830,12 @@ in {
   rabbitmq = handleTest ./rabbitmq.nix {};
   radarr = handleTest ./radarr.nix {};
   radicale = handleTest ./radicale.nix {};
+  radicle = runTest ./radicle.nix;
   ragnarwm = handleTest ./ragnarwm.nix {};
   rasdaemon = handleTest ./rasdaemon.nix {};
+  rathole = handleTest ./rathole.nix {};
   readarr = handleTest ./readarr.nix {};
+  realm = handleTest ./realm.nix {};
   redis = handleTest ./redis.nix {};
   redlib = handleTest ./redlib.nix {};
   redmine = handleTest ./redmine.nix {};
@@ -969,11 +999,13 @@ in {
   teeworlds = handleTest ./teeworlds.nix {};
   telegraf = handleTest ./telegraf.nix {};
   teleport = handleTest ./teleport.nix {};
+  teleports = runTest ./teleports.nix;
   thelounge = handleTest ./thelounge.nix {};
   terminal-emulators = handleTest ./terminal-emulators.nix {};
   thanos = handleTest ./thanos.nix {};
   tiddlywiki = handleTest ./tiddlywiki.nix {};
   tigervnc = handleTest ./tigervnc.nix {};
+  tika = runTest ./tika.nix;
   timescaledb = handleTest ./timescaledb.nix {};
   timezone = handleTest ./timezone.nix {};
   tinc = handleTest ./tinc {};
@@ -1041,6 +1073,7 @@ in {
   wastebin = handleTest ./wastebin.nix {};
   watchdogd = handleTest ./watchdogd.nix {};
   webhook = runTest ./webhook.nix;
+  weblate = handleTest ./web-apps/weblate.nix {};
   wiki-js = handleTest ./wiki-js.nix {};
   wine = handleTest ./wine.nix {};
   wireguard = handleTest ./wireguard {};
diff --git a/nixos/tests/armagetronad.nix b/nixos/tests/armagetronad.nix
index ca93ce8fb6c5d..b657893fc9eef 100644
--- a/nixos/tests/armagetronad.nix
+++ b/nixos/tests/armagetronad.nix
@@ -13,7 +13,7 @@ let
 
     { imports = [ ./common/user-account.nix ./common/x11.nix ];
       hardware.graphics.enable = true;
-      virtualisation.memorySize = 256;
+      virtualisation.memorySize = 384;
       environment = {
         systemPackages = [ pkgs.armagetronad ];
         variables.XAUTHORITY = "/home/${user}/.Xauthority";
@@ -208,7 +208,7 @@ makeTest {
         barrier.wait()
 
       # Get to the Server Bookmarks screen on both clients. This takes a while so do it asynchronously.
-      barrier = threading.Barrier(3, timeout=120)
+      barrier = threading.Barrier(len(clients) + 1, timeout=240)
       for client in clients:
         threading.Thread(target=client_setup, args=(client, servers, barrier)).start()
       barrier.wait()
diff --git a/nixos/tests/ayatana-indicators.nix b/nixos/tests/ayatana-indicators.nix
index ccb1e059a6973..cfd4d8099d112 100644
--- a/nixos/tests/ayatana-indicators.nix
+++ b/nixos/tests/ayatana-indicators.nix
@@ -35,7 +35,7 @@ in {
         ayatana-indicator-sound
       ] ++ (with pkgs.lomiri; [
         lomiri-indicator-network
-        # telephony-service # currently broken: https://github.com/NixOS/nixpkgs/pull/314043
+        telephony-service
       ]);
     };
 
diff --git a/nixos/tests/bind.nix b/nixos/tests/bind.nix
index 15accbd49db43..95a9fc4e58bbf 100644
--- a/nixos/tests/bind.nix
+++ b/nixos/tests/bind.nix
@@ -22,7 +22,6 @@ import ./make-test-python.nix {
 
   testScript = ''
     machine.wait_for_unit("bind.service")
-    machine.wait_for_open_port(53)
     machine.succeed("host 192.168.0.1 127.0.0.1 | grep -qF ns.example.org")
   '';
 }
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index b5f5982743a13..565e37cf0e56d 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -21,7 +21,7 @@ let
 
   download-dir = "/var/lib/transmission/Downloads";
   transmissionConfig = { ... }: {
-    environment.systemPackages = [ pkgs.transmission ];
+    environment.systemPackages = [ pkgs.transmission_3 ];
     services.transmission = {
       enable = true;
       settings = {
diff --git a/nixos/tests/borgmatic.nix b/nixos/tests/borgmatic.nix
new file mode 100644
index 0000000000000..70ad43e8bd358
--- /dev/null
+++ b/nixos/tests/borgmatic.nix
@@ -0,0 +1,24 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+{
+  name = "borgmatic";
+  nodes.machine = { ... }: {
+    services.borgmatic = {
+      enable = true;
+      settings = {
+        source_directories = [ "/home" ];
+        repositories = [
+          {
+            label = "local";
+            path = "/var/backup";
+          }
+        ];
+        keep_daily = 7;
+      };
+    };
+  };
+
+  testScript = ''
+    machine.succeed("borgmatic rcreate -e none")
+    machine.succeed("borgmatic")
+  '';
+})
diff --git a/nixos/tests/bpf.nix b/nixos/tests/bpf.nix
index 150ed0958862e..0020c7ee2d693 100644
--- a/nixos/tests/bpf.nix
+++ b/nixos/tests/bpf.nix
@@ -16,14 +16,14 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     # list probes
     machine.succeed("bpftrace -l")
     # simple BEGIN probe (user probe on bpftrace itself)
-    print(machine.succeed("bpftrace -e 'BEGIN { print(\"ok\"); exit(); }'"))
+    print(machine.succeed("bpftrace -e 'BEGIN { print(\"ok\\n\"); exit(); }'"))
     # tracepoint
     print(machine.succeed("bpftrace -e 'tracepoint:syscalls:sys_enter_* { print(probe); exit() }'"))
     # kprobe
     print(machine.succeed("bpftrace -e 'kprobe:schedule { print(probe); exit() }'"))
     # BTF
     print(machine.succeed("bpftrace -e 'kprobe:schedule { "
-        "    printf(\"tgid: %d\", ((struct task_struct*) curtask)->tgid); exit() "
+        "    printf(\"tgid: %d\\n\", ((struct task_struct*) curtask)->tgid); exit() "
         "}'"))
     # module BTF (bpftrace >= 0.17)
     # test is currently disabled on aarch64 as kfunc does not work there yet
@@ -32,5 +32,8 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         "bpftrace -e 'kfunc:nft_trans_alloc_gfp { "
         "    printf(\"portid: %d\\n\", args->ctx->portid); "
         "} BEGIN { exit() }'"))
+    # glibc includes
+    print(machine.succeed("bpftrace -e '#include <errno.h>\n"
+        "BEGIN { printf(\"ok %d\\n\", EINVAL); exit(); }'"))
   '';
 })
diff --git a/nixos/tests/budgie.nix b/nixos/tests/budgie.nix
index 203e718c8c6d9..9f24ea71de1d4 100644
--- a/nixos/tests/budgie.nix
+++ b/nixos/tests/budgie.nix
@@ -25,7 +25,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     services.xserver.desktopManager.budgie = {
       enable = true;
       extraPlugins = [
-        pkgs.budgiePlugins.budgie-analogue-clock-applet
+        pkgs.budgie-analogue-clock-applet
       ];
     };
   };
@@ -63,7 +63,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       with subtest("Check if various environment variables are set"):
           cmd = "xargs --null --max-args=1 echo < /proc/$(pgrep -xf /run/current-system/sw/bin/budgie-wm)/environ"
           machine.succeed(f"{cmd} | grep 'XDG_CURRENT_DESKTOP' | grep 'Budgie:GNOME'")
-          machine.succeed(f"{cmd} | grep 'BUDGIE_PLUGIN_DATADIR' | grep '${pkgs.budgie.budgie-desktop-with-plugins.pname}'")
+          machine.succeed(f"{cmd} | grep 'BUDGIE_PLUGIN_DATADIR' | grep '${pkgs.budgie-desktop-with-plugins.pname}'")
 
       with subtest("Open run dialog"):
           machine.send_key("alt-f2")
diff --git a/nixos/tests/common/acme/client/default.nix b/nixos/tests/common/acme/client/default.nix
index 503e610d1ac9e..f9b08b519dbeb 100644
--- a/nixos/tests/common/acme/client/default.nix
+++ b/nixos/tests/common/acme/client/default.nix
@@ -1,4 +1,4 @@
-{ lib, nodes, pkgs, ... }:
+{ nodes, ... }:
 let
   caCert = nodes.acme.test-support.acme.caCert;
   caDomain = nodes.acme.test-support.acme.caDomain;
diff --git a/nixos/tests/crabfit.nix b/nixos/tests/crabfit.nix
index 0daf47d52f25d..eb38a0ae0cfcd 100644
--- a/nixos/tests/crabfit.nix
+++ b/nixos/tests/crabfit.nix
@@ -4,7 +4,7 @@ import ./make-test-python.nix (
   {
     name = "crabfit";
 
-    meta.maintainers = with lib.maintainers; [ ];
+    meta.maintainers = [ ];
 
     nodes = {
       machine =
diff --git a/nixos/tests/cryptpad.nix b/nixos/tests/cryptpad.nix
new file mode 100644
index 0000000000000..9d6af15f5f862
--- /dev/null
+++ b/nixos/tests/cryptpad.nix
@@ -0,0 +1,71 @@
+{ pkgs, ... }:
+let
+  certs = pkgs.runCommand "cryptpadSelfSignedCerts" { buildInputs = [ pkgs.openssl ]; } ''
+    mkdir -p $out
+    cd $out
+    openssl req -x509 -newkey rsa:4096 \
+      -keyout key.pem -out cert.pem -nodes -days 3650 \
+      -subj '/CN=cryptpad.localhost' \
+      -addext 'subjectAltName = DNS.1:cryptpad.localhost, DNS.2:cryptpad-sandbox.localhost'
+  '';
+  # data sniffed from cryptpad's /checkup network trace, seems to be re-usable
+  test_write_data = pkgs.writeText "cryptpadTestData" ''
+    {"command":"WRITE_BLOCK","content":{"publicKey":"O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik=","signature":"aXcM9SMO59lwA7q7HbYB+AnzymmxSyy/KhkG/cXIBVzl8v+kkPWXmFuWhcuKfRF8yt3Zc3ktIsHoFyuyDSAwAA==","ciphertext":"AFwCIfBHKdFzDKjMg4cu66qlJLpP+6Yxogbl3o9neiQou5P8h8yJB8qgnQ=="},"publicKey":"O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik=","nonce":"bitSbJMNSzOsg98nEzN80a231PCkBQeH"}
+  '';
+in
+{
+  name = "cryptpad";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ martinetd ];
+  };
+
+  nodes.machine = {
+    services.cryptpad = {
+      enable = true;
+      configureNginx = true;
+      settings = {
+        httpUnsafeOrigin = "https://cryptpad.localhost";
+        httpSafeOrigin = "https://cryptpad-sandbox.localhost";
+      };
+    };
+    services.nginx = {
+      virtualHosts."cryptpad.localhost" = {
+        enableACME = false;
+        sslCertificate = "${certs}/cert.pem";
+        sslCertificateKey = "${certs}/key.pem";
+      };
+    };
+    security = {
+      pki.certificateFiles = [ "${certs}/cert.pem" ];
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("cryptpad.service")
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_open_port(3000)
+
+    # test home page
+    machine.succeed("curl --fail https://cryptpad.localhost -o /tmp/cryptpad_home.html")
+    machine.succeed("grep -F 'CryptPad: Collaboration suite' /tmp/cryptpad_home.html")
+
+    # test scripts/build.js actually generated customize content from config
+    machine.succeed("grep -F 'meta property=\"og:url\" content=\"https://cryptpad.localhost/index.html' /tmp/cryptpad_home.html")
+
+    # make sure child pages are accessible (e.g. check nginx try_files paths)
+    machine.succeed(
+        "grep -oE '/(customize|components)[^\"]*' /tmp/cryptpad_home.html"
+        "  | while read -r page; do"
+        "        curl -O --fail https://cryptpad.localhost$page || exit;"
+        "    done")
+
+    # test some API (e.g. check cryptpad main process)
+    machine.succeed("curl --fail -d @${test_write_data} -H 'Content-Type: application/json' https://cryptpad.localhost/api/auth")
+
+    # test telemetry has been disabled
+    machine.fail("journalctl -u cryptpad | grep TELEMETRY");
+
+    # for future improvements
+    machine.log(machine.execute("systemd-analyze security cryptpad.service")[1])
+  '';
+}
diff --git a/nixos/tests/darling-dmg.nix b/nixos/tests/darling-dmg.nix
new file mode 100644
index 0000000000000..0a2ec82981228
--- /dev/null
+++ b/nixos/tests/darling-dmg.nix
@@ -0,0 +1,34 @@
+{ lib, pkgs, ... }:
+# This needs to be a VM test because the FUSE kernel module can't be used inside of a derivation in the Nix sandbox.
+# This test also exercises the LZFSE support in darling-dmg.
+let
+  # The last kitty release which is stored on an HFS+ filesystem inside the disk image
+  test-dmg-file = pkgs.fetchurl {
+    url = "https://github.com/kovidgoyal/kitty/releases/download/v0.17.4/kitty-0.17.4.dmg";
+    hash = "sha256-m+c5s8fFrgUc0xQNI196WplYBZq9+lNgems5haZUdvA=";
+  };
+in
+{
+  name = "darling-dmg";
+  meta.maintainers = with lib.maintainers; [ Luflosi ];
+
+  nodes.machine = {};
+
+  testScript = ''
+    start_all()
+
+    machine.succeed("mkdir mount-point")
+    machine.succeed("'${pkgs.darling-dmg}/bin/darling-dmg' '${test-dmg-file}' mount-point")
+
+    # Crude way to verify the contents
+    # Taken from https://stackoverflow.com/questions/545387/linux-compute-a-single-hash-for-a-given-folder-contents
+    # This could be improved. It does not check symlinks for example.
+    hash = machine.succeed("""
+      (find mount-point -type f -print0  | sort -z | xargs -0 sha256sum; \
+       find mount-point \( -type f -o -type d \) -print0 | sort -z | \
+         xargs -0 stat -c '%n %a') \
+      | sha256sum
+    """).strip()
+    assert hash == "00e61c2ef171093fbf194e420c17bb84bcdb823238d70eb46e375bab2427cc21  -", f"The disk image contents differ from what was expected (was {hash})"
+  '';
+}
diff --git a/nixos/tests/ddns-updater.nix b/nixos/tests/ddns-updater.nix
new file mode 100644
index 0000000000000..caa763e09bba3
--- /dev/null
+++ b/nixos/tests/ddns-updater.nix
@@ -0,0 +1,28 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+  let
+    port = 6000;
+  in
+  {
+    name = "ddns-updater";
+
+    meta.maintainers = with lib.maintainers; [ delliott ];
+
+    nodes.machine =
+      { pkgs, ... }:
+      {
+        services.ddns-updater = {
+          enable = true;
+          environment = {
+            LISTENING_ADDRESS = ":" + (toString port);
+          };
+        };
+      };
+
+    testScript = ''
+      machine.wait_for_unit("ddns-updater.service")
+      machine.wait_for_open_port(${toString port})
+      machine.succeed("curl --fail http://localhost:${toString port}/")
+    '';
+  }
+)
diff --git a/nixos/tests/docker-tools-nix-shell.nix b/nixos/tests/docker-tools-nix-shell.nix
new file mode 100644
index 0000000000000..c2ae2124e0a18
--- /dev/null
+++ b/nixos/tests/docker-tools-nix-shell.nix
@@ -0,0 +1,95 @@
+# nix-build -A nixosTests.docker-tools-nix-shell
+{ config, lib, ... }:
+let
+  inherit (config.node.pkgs.dockerTools) examples;
+in
+{
+  name = "docker-tools-nix-shell";
+  meta = with lib.maintainers; {
+    maintainers = [
+      infinisil
+      roberth
+    ];
+  };
+
+  nodes = {
+    docker =
+      { ... }:
+      {
+        virtualisation = {
+          diskSize = 3072;
+          docker.enable = true;
+        };
+      };
+  };
+
+  testScript = ''
+    docker.wait_for_unit("sockets.target")
+
+    with subtest("buildImageWithNixDB: Has a nix database"):
+        docker.succeed(
+            "docker load --input='${examples.nix}'",
+            "docker run --rm ${examples.nix.imageName} nix-store -q --references /bin/bash"
+        )
+
+    with subtest("buildNixShellImage: Can build a basic derivation"):
+        docker.succeed(
+            "${examples.nix-shell-basic} | docker load",
+            "docker run --rm nix-shell-basic bash -c 'buildDerivation && $out/bin/hello' | grep '^Hello, world!$'"
+        )
+
+    with subtest("buildNixShellImage: Runs the shell hook"):
+        docker.succeed(
+            "${examples.nix-shell-hook} | docker load",
+            "docker run --rm -it nix-shell-hook | grep 'This is the shell hook!'"
+        )
+
+    with subtest("buildNixShellImage: Sources stdenv, making build inputs available"):
+        docker.succeed(
+            "${examples.nix-shell-inputs} | docker load",
+            "docker run --rm -it nix-shell-inputs | grep 'Hello, world!'"
+        )
+
+    with subtest("buildNixShellImage: passAsFile works"):
+        docker.succeed(
+            "${examples.nix-shell-pass-as-file} | docker load",
+            "docker run --rm -it nix-shell-pass-as-file | grep 'this is a string'"
+        )
+
+    with subtest("buildNixShellImage: run argument works"):
+        docker.succeed(
+            "${examples.nix-shell-run} | docker load",
+            "docker run --rm -it nix-shell-run | grep 'This shell is not interactive'"
+        )
+
+    with subtest("buildNixShellImage: command argument works"):
+        docker.succeed(
+            "${examples.nix-shell-command} | docker load",
+            "docker run --rm -it nix-shell-command | grep 'This shell is interactive'"
+        )
+
+    with subtest("buildNixShellImage: home directory is writable by default"):
+        docker.succeed(
+            "${examples.nix-shell-writable-home} | docker load",
+            "docker run --rm -it nix-shell-writable-home"
+        )
+
+    with subtest("buildNixShellImage: home directory can be made non-existent"):
+        docker.succeed(
+            "${examples.nix-shell-nonexistent-home} | docker load",
+            "docker run --rm -it nix-shell-nonexistent-home"
+        )
+
+    with subtest("buildNixShellImage: can build derivations"):
+        docker.succeed(
+            "${examples.nix-shell-build-derivation} | docker load",
+            "docker run --rm -it nix-shell-build-derivation"
+        )
+
+    with subtest("streamLayeredImage: with nix db"):
+        docker.succeed(
+            "${examples.nix-layered} | docker load",
+            "docker run --rm ${examples.nix-layered.imageName} nix-store -q --references /bin/bash"
+        )
+  '';
+}
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index c8a227eb2cf7b..41bd4a621545f 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -60,7 +60,7 @@ let
     };
 
   nonRootTestImage =
-    pkgs.dockerTools.streamLayeredImage rec {
+    pkgs.dockerTools.streamLayeredImage {
       name = "non-root-test";
       tag = "latest";
       uid = 1000;
@@ -567,60 +567,6 @@ in {
         docker.succeed("docker run --rm image-with-certs:latest test -r /etc/pki/tls/certs/ca-bundle.crt")
         docker.succeed("docker image rm image-with-certs:latest")
 
-    with subtest("buildNixShellImage: Can build a basic derivation"):
-        docker.succeed(
-            "${examples.nix-shell-basic} | docker load",
-            "docker run --rm nix-shell-basic bash -c 'buildDerivation && $out/bin/hello' | grep '^Hello, world!$'"
-        )
-
-    with subtest("buildNixShellImage: Runs the shell hook"):
-        docker.succeed(
-            "${examples.nix-shell-hook} | docker load",
-            "docker run --rm -it nix-shell-hook | grep 'This is the shell hook!'"
-        )
-
-    with subtest("buildNixShellImage: Sources stdenv, making build inputs available"):
-        docker.succeed(
-            "${examples.nix-shell-inputs} | docker load",
-            "docker run --rm -it nix-shell-inputs | grep 'Hello, world!'"
-        )
-
-    with subtest("buildNixShellImage: passAsFile works"):
-        docker.succeed(
-            "${examples.nix-shell-pass-as-file} | docker load",
-            "docker run --rm -it nix-shell-pass-as-file | grep 'this is a string'"
-        )
-
-    with subtest("buildNixShellImage: run argument works"):
-        docker.succeed(
-            "${examples.nix-shell-run} | docker load",
-            "docker run --rm -it nix-shell-run | grep 'This shell is not interactive'"
-        )
-
-    with subtest("buildNixShellImage: command argument works"):
-        docker.succeed(
-            "${examples.nix-shell-command} | docker load",
-            "docker run --rm -it nix-shell-command | grep 'This shell is interactive'"
-        )
-
-    with subtest("buildNixShellImage: home directory is writable by default"):
-        docker.succeed(
-            "${examples.nix-shell-writable-home} | docker load",
-            "docker run --rm -it nix-shell-writable-home"
-        )
-
-    with subtest("buildNixShellImage: home directory can be made non-existent"):
-        docker.succeed(
-            "${examples.nix-shell-nonexistent-home} | docker load",
-            "docker run --rm -it nix-shell-nonexistent-home"
-        )
-
-    with subtest("buildNixShellImage: can build derivations"):
-        docker.succeed(
-            "${examples.nix-shell-build-derivation} | docker load",
-            "docker run --rm -it nix-shell-build-derivation"
-        )
-
     with subtest("streamLayeredImage: chown is persistent in fakeRootCommands"):
         docker.succeed(
             "${chownTestImage} | docker load",
diff --git a/nixos/tests/domination.nix b/nixos/tests/domination.nix
index 9e4badd2e369d..04899c5065311 100644
--- a/nixos/tests/domination.nix
+++ b/nixos/tests/domination.nix
@@ -10,7 +10,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     ];
 
     services.xserver.enable = true;
-    sound.enable = true;
     environment.systemPackages = [ pkgs.domination ];
   };
 
diff --git a/nixos/tests/druid/default.nix b/nixos/tests/druid/default.nix
new file mode 100644
index 0000000000000..d4b7c9bffa772
--- /dev/null
+++ b/nixos/tests/druid/default.nix
@@ -0,0 +1,289 @@
+{ pkgs, ... }:
+let
+  inherit (pkgs) lib;
+  commonConfig = {
+    "druid.zk.service.host" = "zk1:2181";
+    "druid.extensions.loadList" = ''[ "druid-histogram", "druid-datasketches",  "mysql-metadata-storage", "druid-avro-extensions", "druid-parquet-extensions", "druid-lookups-cached-global", "druid-hdfs-storage","druid-kafka-indexing-service","druid-basic-security","druid-kinesis-indexing-service"]'';
+    "druid.startup.logging.logProperties" = "true";
+    "druid.metadata.storage.connector.connectURI" = "jdbc:mysql://mysql:3306/druid";
+    "druid.metadata.storage.connector.user" = "druid";
+    "druid.metadata.storage.connector.password" = "druid";
+    "druid.request.logging.type" = "file";
+    "druid.request.logging.dir" = "/var/log/druid/requests";
+    "druid.javascript.enabled" = "true";
+    "druid.sql.enable" = "true";
+    "druid.metadata.storage.type" = "mysql";
+    "druid.storage.type" = "hdfs";
+    "druid.storage.storageDirectory" = "/druid-deepstore";
+  };
+  log4jConfig = ''
+    <?xml version="1.0" encoding="UTF-8" ?>
+    <Configuration status="WARN">
+     <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+          <PatternLayout pattern="%d{ISO8601} %p [%t] %c - %m%n"/>
+        </Console>
+      </Appenders>
+      <Loggers>
+        <Root level="error">
+          <AppenderRef ref="Console"/>
+        </Root>
+      </Loggers>
+    </Configuration>
+  '';
+  log4j = pkgs.writeText "log4j2.xml" log4jConfig;
+  coreSite = {
+    "fs.defaultFS" = "hdfs://namenode:8020";
+  };
+  tests = {
+    default = testsForPackage {
+      druidPackage = pkgs.druid;
+      hadoopPackage = pkgs.hadoop_3_2;
+    };
+  };
+  testsForPackage =
+    args:
+    lib.recurseIntoAttrs {
+      druidCluster = testDruidCluster args;
+      passthru.override = args': testsForPackage (args // args');
+    };
+  testDruidCluster =
+    { druidPackage, hadoopPackage, ... }:
+    pkgs.testers.nixosTest {
+      name = "druid-hdfs";
+      nodes = {
+        zk1 =
+          { ... }:
+          {
+            services.zookeeper.enable = true;
+            networking.firewall.allowedTCPPorts = [ 2181 ];
+          };
+        namenode =
+          { ... }:
+          {
+            services.hadoop = {
+              package = hadoopPackage;
+              hdfs = {
+                namenode = {
+                  enable = true;
+                  openFirewall = true;
+                  formatOnInit = true;
+                };
+              };
+              inherit coreSite;
+            };
+          };
+        datanode =
+          { ... }:
+          {
+            services.hadoop = {
+              package = hadoopPackage;
+              hdfs.datanode = {
+                enable = true;
+                openFirewall = true;
+              };
+              inherit coreSite;
+            };
+          };
+        mm =
+          { ... }:
+          {
+            virtualisation.memorySize = 1024;
+            services.druid = {
+              inherit commonConfig log4j;
+              package = druidPackage;
+              extraClassPaths = [ "/etc/hadoop-conf" ];
+              middleManager = {
+                config = {
+                  "druid.indexer.task.baseTaskDir" = "/tmp/druid/persistent/task";
+                  "druid.worker.capacity" = 1;
+                  "druid.indexer.logs.type" = "file";
+                  "druid.indexer.logs.directory" = "/var/log/druid/indexer";
+                  "druid.indexer.runner.startPort" = 8100;
+                  "druid.indexer.runner.endPort" = 8101;
+                };
+                enable = true;
+                openFirewall = true;
+              };
+            };
+            services.hadoop = {
+              gatewayRole.enable = true;
+              package = hadoopPackage;
+              inherit coreSite;
+            };
+          };
+        overlord =
+          { ... }:
+          {
+            services.druid = {
+              inherit commonConfig log4j;
+              package = druidPackage;
+              extraClassPaths = [ "/etc/hadoop-conf" ];
+              overlord = {
+                config = {
+                  "druid.indexer.runner.type" = "remote";
+                  "druid.indexer.storage.type" = "metadata";
+                };
+                enable = true;
+                openFirewall = true;
+              };
+            };
+            services.hadoop = {
+              gatewayRole.enable = true;
+              package = hadoopPackage;
+              inherit coreSite;
+            };
+          };
+        broker =
+          { ... }:
+          {
+            services.druid = {
+              package = druidPackage;
+              inherit commonConfig log4j;
+              extraClassPaths = [ "/etc/hadoop-conf" ];
+              broker = {
+                config = {
+                  "druid.plaintextPort" = 8082;
+                  "druid.broker.http.numConnections" = "2";
+                  "druid.server.http.numThreads" = "2";
+                  "druid.processing.buffer.sizeBytes" = "100";
+                  "druid.processing.numThreads" = "1";
+                  "druid.processing.numMergeBuffers" = "1";
+                  "druid.broker.cache.unCacheable" = ''["groupBy"]'';
+                  "druid.lookup.snapshotWorkingDir" = "/opt/broker/lookups";
+                };
+                enable = true;
+                openFirewall = true;
+              };
+            };
+            services.hadoop = {
+              gatewayRole.enable = true;
+              package = hadoopPackage;
+              inherit coreSite;
+            };
+
+          };
+        historical =
+          { ... }:
+          {
+            services.druid = {
+              package = druidPackage;
+              inherit commonConfig log4j;
+              extraClassPaths = [ "/etc/hadoop-conf" ];
+              historical = {
+                config = {
+                  "maxSize" = 200000000;
+                  "druid.lookup.snapshotWorkingDir" = "/opt/historical/lookups";
+                };
+                segmentLocations = [
+                  {
+                    "path" = "/tmp/1";
+                    "maxSize" = "100000000";
+                  }
+                  {
+                    "path" = "/tmp/2";
+                    "maxSize" = "100000000";
+                  }
+                ];
+                enable = true;
+                openFirewall = true;
+              };
+            };
+            services.hadoop = {
+              gatewayRole.enable = true;
+              package = hadoopPackage;
+              inherit coreSite;
+            };
+
+          };
+        coordinator =
+          { ... }:
+          {
+            services.druid = {
+              package = druidPackage;
+              inherit commonConfig log4j;
+              extraClassPaths = [ "/etc/hadoop-conf" ];
+              coordinator = {
+                config = {
+                  "druid.plaintextPort" = 9091;
+                  "druid.service" = "coordinator";
+                  "druid.coordinator.startDelay" = "PT10S";
+                  "druid.coordinator.period" = "PT10S";
+                  "druid.manager.config.pollDuration" = "PT10S";
+                  "druid.manager.segments.pollDuration" = "PT10S";
+                  "druid.manager.rules.pollDuration" = "PT10S";
+                };
+                enable = true;
+                openFirewall = true;
+              };
+            };
+            services.hadoop = {
+              gatewayRole.enable = true;
+              package = hadoopPackage;
+              inherit coreSite;
+            };
+
+          };
+
+        mysql =
+          { ... }:
+          {
+            services.mysql = {
+              enable = true;
+              package = pkgs.mariadb;
+              initialDatabases = [ { name = "druid"; } ];
+              initialScript = pkgs.writeText "mysql-init.sql" ''
+                CREATE USER 'druid'@'%' IDENTIFIED BY 'druid';
+                GRANT ALL PRIVILEGES ON druid.* TO 'druid'@'%';
+              '';
+            };
+            networking.firewall.allowedTCPPorts = [ 3306 ];
+          };
+
+      };
+      testScript = ''
+        start_all()
+        namenode.wait_for_unit("hdfs-namenode")
+        namenode.wait_for_unit("network.target")
+        namenode.wait_for_open_port(8020)
+        namenode.succeed("ss -tulpne | systemd-cat")
+        namenode.succeed("cat /etc/hadoop*/hdfs-site.xml | systemd-cat")
+        namenode.wait_for_open_port(9870)
+        datanode.wait_for_unit("hdfs-datanode")
+        datanode.wait_for_unit("network.target")
+
+        mm.succeed("mkdir -p /quickstart/")
+        mm.succeed("cp -r ${pkgs.druid}/quickstart/* /quickstart/")
+        mm.succeed("touch /quickstart/tutorial/wikiticker-2015-09-12-sampled.json")
+        mm.succeed("zcat /quickstart/tutorial/wikiticker-2015-09-12-sampled.json.gz | head -n 10 > /quickstart/tutorial/wikiticker-2015-09-12-sampled.json || true")
+        mm.succeed("rm /quickstart/tutorial/wikiticker-2015-09-12-sampled.json.gz && gzip /quickstart/tutorial/wikiticker-2015-09-12-sampled.json")
+
+        namenode.succeed("sudo -u hdfs hdfs dfs -mkdir /druid-deepstore")
+        namenode.succeed("HADOOP_USER_NAME=druid sudo -u hdfs hdfs dfs -chown druid:hadoop /druid-deepstore")
+
+
+        ### Druid tests
+        coordinator.wait_for_unit("druid-coordinator")
+        overlord.wait_for_unit("druid-overlord")
+        historical.wait_for_unit("druid-historical")
+        mm.wait_for_unit("druid-middleManager")
+
+        coordinator.wait_for_open_port(9091)
+        overlord.wait_for_open_port(8090)
+        historical.wait_for_open_port(8083)
+        mm.wait_for_open_port(8091)
+
+        broker.wait_for_unit("network.target")
+        broker.wait_for_open_port(8082)
+
+        broker.succeed("curl -X 'POST' -H 'Content-Type:application/json' -d @${pkgs.druid}/quickstart/tutorial/wikipedia-index.json http://coordinator:9091/druid/indexer/v1/task")
+        broker.wait_until_succeeds("curl http://coordinator:9091/druid/coordinator/v1/metadata/datasources | grep  'wikipedia'")
+
+        broker.wait_until_succeeds("curl http://localhost:8082/druid/v2/datasources/ | grep wikipedia")
+        broker.succeed("curl -X 'POST' -H 'Content-Type:application/json' -d @${pkgs.druid}/quickstart/tutorial/wikipedia-top-pages.json http://localhost:8082/druid/v2/")
+
+      '';
+
+    };
+in
+tests
diff --git a/nixos/tests/eintopf.nix b/nixos/tests/eintopf.nix
new file mode 100644
index 0000000000000..a1c05d6513041
--- /dev/null
+++ b/nixos/tests/eintopf.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix ({ pkgs, ...} : {
+  name = "eintopf";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ onny ];
+  };
+
+  nodes = {
+    eintopf = { config, pkgs, ... }: {
+      services.eintopf = {
+        enable = true;
+      };
+    };
+  };
+
+  testScript = ''
+    eintopf.start
+    eintopf.wait_for_unit("eintopf.service")
+    eintopf.wait_for_open_port(3333)
+    eintopf.succeed("curl -sSfL http://eintopf:3333 | grep 'Es sind keine Veranstaltungen eingetragen'")
+  '';
+})
diff --git a/nixos/tests/firefox.nix b/nixos/tests/firefox.nix
index 6418e029f80d9..8243defbb9f2e 100644
--- a/nixos/tests/firefox.nix
+++ b/nixos/tests/firefox.nix
@@ -21,8 +21,7 @@ import ./make-test-python.nix ({ lib, pkgs, firefoxPackage, ... }:
       # Create a virtual sound device, with mixing
       # and all, for recording audio.
       boot.kernelModules = [ "snd-aloop" ];
-      sound.enable = true;
-      sound.extraConfig = ''
+      environment.etc."asound.conf".text = ''
         pcm.!default {
           type plug
           slave.pcm pcm.dmixer
diff --git a/nixos/tests/firewall.nix b/nixos/tests/firewall.nix
index 34e8bda60eef5..ad418bb3341f0 100644
--- a/nixos/tests/firewall.nix
+++ b/nixos/tests/firewall.nix
@@ -36,7 +36,7 @@ import ./make-test-python.nix ( { pkgs, nftables, ... } : {
     };
 
   testScript = { nodes, ... }: let
-    newSystem = nodes.walled2.config.system.build.toplevel;
+    newSystem = nodes.walled2.system.build.toplevel;
     unit = if nftables then "nftables" else "firewall";
   in ''
     start_all()
diff --git a/nixos/tests/flaresolverr.nix b/nixos/tests/flaresolverr.nix
new file mode 100644
index 0000000000000..0cec7adf6d6b6
--- /dev/null
+++ b/nixos/tests/flaresolverr.nix
@@ -0,0 +1,22 @@
+import ./make-test-python.nix (
+  { lib, ... }:
+  {
+    name = "flaresolverr";
+    meta.maintainers = with lib.maintainers; [ paveloom ];
+
+    nodes.machine =
+      { pkgs, ... }:
+      {
+        services.flaresolverr = {
+          enable = true;
+          port = 8888;
+        };
+      };
+
+    testScript = ''
+      machine.wait_for_unit("flaresolverr.service")
+      machine.wait_for_open_port(8888)
+      machine.succeed("curl --fail http://localhost:8888/")
+    '';
+  }
+)
diff --git a/nixos/tests/forgejo.nix b/nixos/tests/forgejo.nix
index c5bf8e32524cb..d2315b7f013eb 100644
--- a/nixos/tests/forgejo.nix
+++ b/nixos/tests/forgejo.nix
@@ -1,6 +1,7 @@
 { system ? builtins.currentSystem
 , config ? { }
 , pkgs ? import ../.. { inherit system config; }
+, forgejoPackage ? pkgs.forgejo
 }:
 
 with import ../lib/testing-python.nix { inherit system pkgs; };
@@ -53,6 +54,7 @@ let
         virtualisation.memorySize = 2047;
         services.forgejo = {
           enable = true;
+          package = forgejoPackage;
           database = { inherit type; };
           settings.service.DISABLE_REGISTRATION = true;
           settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
@@ -145,7 +147,7 @@ let
         assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
 
         api_version = json.loads(server.succeed("curl http://localhost:3000/api/forgejo/v1/version")).get("version")
-        assert "development" != api_version and "${pkgs.forgejo.version}+gitea-" in api_version, (
+        assert "development" != api_version and "${forgejoPackage.version}+gitea-" in api_version, (
             "/api/forgejo/v1/version should not return 'development' "
             + f"but should contain a forgejo+gitea compatibility version string. Got '{api_version}' instead."
         )
diff --git a/nixos/tests/ft2-clone.nix b/nixos/tests/ft2-clone.nix
index 5476b38c00bd2..813e258cd2bec 100644
--- a/nixos/tests/ft2-clone.nix
+++ b/nixos/tests/ft2-clone.nix
@@ -8,8 +8,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     imports = [
       ./common/x11.nix
     ];
-
-    sound.enable = true;
     environment.systemPackages = [ pkgs.ft2-clone ];
   };
 
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index 52fe588930dfc..a099a8201ae50 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -34,6 +34,8 @@ in {
     gitlab = { ... }: {
       imports = [ common/user-account.nix ];
 
+      environment.systemPackages = with pkgs; [ git ];
+
       virtualisation.memorySize = 6144;
       virtualisation.cores = 4;
       virtualisation.useNixStoreImage = true;
diff --git a/nixos/tests/gitolite-fcgiwrap.nix b/nixos/tests/gitolite-fcgiwrap.nix
index 6e8dae6f72d73..43d65faebbee2 100644
--- a/nixos/tests/gitolite-fcgiwrap.nix
+++ b/nixos/tests/gitolite-fcgiwrap.nix
@@ -24,7 +24,7 @@ import ./make-test-python.nix (
               {
                 networking.firewall.allowedTCPPorts = [ 80 ];
 
-                services.fcgiwrap.gitolite = {
+                services.fcgiwrap.instances.gitolite = {
                   process.user = "gitolite";
                   process.group = "gitolite";
                   socket = { inherit (config.services.nginx) user group; };
@@ -64,7 +64,7 @@ import ./make-test-python.nix (
                     fastcgi_param SCRIPT_FILENAME ${pkgs.gitolite}/bin/gitolite-shell;
 
                     # use Unix domain socket or inet socket
-                    fastcgi_pass unix:${config.services.fcgiwrap.gitolite.socket.address};
+                    fastcgi_pass unix:${config.services.fcgiwrap.instances.gitolite.socket.address};
                   '';
                 };
 
diff --git a/nixos/tests/glance.nix b/nixos/tests/glance.nix
new file mode 100644
index 0000000000000..daa3d9a4a8160
--- /dev/null
+++ b/nixos/tests/glance.nix
@@ -0,0 +1,36 @@
+{ lib, ... }:
+
+{
+  name = "glance";
+
+  nodes = {
+    machine_default =
+      { pkgs, ... }:
+      {
+        services.glance = {
+          enable = true;
+        };
+      };
+
+    machine_custom_port =
+      { pkgs, ... }:
+      {
+        services.glance = {
+          enable = true;
+          settings.server.port = 5678;
+        };
+      };
+  };
+
+  testScript = ''
+    machine_default.start()
+    machine_default.wait_for_unit("glance.service")
+    machine_default.wait_for_open_port(8080)
+
+    machine_custom_port.start()
+    machine_custom_port.wait_for_unit("glance.service")
+    machine_custom_port.wait_for_open_port(5678)
+  '';
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+}
diff --git a/nixos/tests/goatcounter.nix b/nixos/tests/goatcounter.nix
new file mode 100644
index 0000000000000..ee3b373383e2d
--- /dev/null
+++ b/nixos/tests/goatcounter.nix
@@ -0,0 +1,32 @@
+import ./make-test-python.nix (
+  { lib, pkgs, ... }:
+
+  {
+    name = "goatcounter";
+
+    meta.maintainers = with lib.maintainers; [ bhankas ];
+
+    nodes.machine =
+      { config, ... }:
+      {
+        virtualisation.memorySize = 2048;
+
+        services.goatcounter = {
+          enable = true;
+          proxy = true;
+        };
+      };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("goatcounter.service")
+      # wait for goatcounter to fully come up
+
+      with subtest("goatcounter service starts"):
+          machine.wait_until_succeeds(
+              "curl -sSfL http://localhost:8081/ > /dev/null",
+              timeout=30
+          )
+    '';
+  }
+)
diff --git a/nixos/tests/gotenberg.nix b/nixos/tests/gotenberg.nix
new file mode 100644
index 0000000000000..aa39b2d349d79
--- /dev/null
+++ b/nixos/tests/gotenberg.nix
@@ -0,0 +1,26 @@
+import ./make-test-python.nix (
+  { lib, ... }:
+
+  {
+    name = "gotenberg";
+    meta.maintainers = with lib.maintainers; [ pyrox0 ];
+
+    nodes.machine = {
+      services.gotenberg = {
+        enable = true;
+      };
+    };
+
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("gotenberg.service")
+
+      # Gotenberg startup
+      machine.wait_for_open_port(3000)
+
+      # Ensure healthcheck endpoint succeeds
+      machine.succeed("curl http://localhost:3000/health")
+    '';
+  }
+)
diff --git a/nixos/tests/gotify-server.nix b/nixos/tests/gotify-server.nix
index c8d7fa172a7b7..495b1c8e3443c 100644
--- a/nixos/tests/gotify-server.nix
+++ b/nixos/tests/gotify-server.nix
@@ -9,7 +9,9 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
 
     services.gotify = {
       enable = true;
-      port = 3000;
+      environment = {
+        GOTIFY_SERVER_PORT = 3000;
+      };
     };
   };
 
diff --git a/nixos/tests/graylog.nix b/nixos/tests/graylog.nix
index 3f7cc3a914390..b52c2976a73f8 100644
--- a/nixos/tests/graylog.nix
+++ b/nixos/tests/graylog.nix
@@ -1,10 +1,10 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }: {
   name = "graylog";
-  meta.maintainers = with lib.maintainers; [ ];
+  meta.maintainers = [ ];
 
   nodes.machine = { pkgs, ... }: {
     virtualisation.memorySize = 4096;
-    virtualisation.diskSize = 4096;
+    virtualisation.diskSize = 1024 * 6;
 
     services.mongodb.enable = true;
     services.elasticsearch.enable = true;
@@ -65,9 +65,18 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
   in ''
     machine.start()
     machine.wait_for_unit("graylog.service")
+
+    machine.wait_until_succeeds(
+      "journalctl -o cat -u graylog.service | grep 'Started REST API at <127.0.0.1:9000>'"
+    )
+
     machine.wait_for_open_port(9000)
     machine.succeed("curl -sSfL http://127.0.0.1:9000/")
 
+    machine.wait_until_succeeds(
+      "journalctl -o cat -u graylog.service | grep 'Graylog server up and running'"
+    )
+
     session = machine.succeed(
         "curl -X POST "
         + "-sSfL http://127.0.0.1:9000/api/system/sessions "
@@ -88,6 +97,10 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
     )
 
     machine.wait_until_succeeds(
+      "journalctl -o cat -u graylog.service | grep -E 'Input \[GELF UDP/Demo/[[:alnum:]]{24}\] is now RUNNING'"
+    )
+
+    machine.wait_until_succeeds(
         "test \"$(curl -sSfL 'http://127.0.0.1:9000/api/cluster/inputstates' "
         + f"-u {session}:session "
         + "-H 'Accept: application/json' "
diff --git a/nixos/tests/hadoop/hadoop.nix b/nixos/tests/hadoop/hadoop.nix
index 6162ccfd33d47..cc631bb468106 100644
--- a/nixos/tests/hadoop/hadoop.nix
+++ b/nixos/tests/hadoop/hadoop.nix
@@ -99,6 +99,7 @@ import ../make-test-python.nix ({ package, ... }: {
       };
 
       dn1 = { ... }: {
+        virtualisation.diskSize = 4096;
         services.hadoop = {
           inherit package coreSite hdfsSite;
           hdfs.datanode = {
diff --git a/nixos/tests/ifm.nix b/nixos/tests/ifm.nix
new file mode 100644
index 0000000000000..60901cb3f7371
--- /dev/null
+++ b/nixos/tests/ifm.nix
@@ -0,0 +1,36 @@
+import ./make-test-python.nix ({ pkgs, ...} :
+
+{
+  name = "ifm";
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [ litchipi ];
+  };
+
+  nodes = {
+    server = rec {
+      services.ifm = {
+        enable = true;
+        port = 9001;
+        dataDir = "/data";
+      };
+
+      system.activationScripts.ifm-setup-dir = ''
+        mkdir -p ${services.ifm.dataDir}
+        chmod u+w,g+w,o+w ${services.ifm.dataDir}
+      '';
+    };
+  };
+
+  testScript = ''
+    start_all()
+    server.wait_for_unit("ifm.service")
+    server.wait_for_open_port(9001)
+    server.succeed("curl --fail http://localhost:9001")
+
+    server.succeed("echo \"testfile\" > testfile && shasum testfile >> checksums")
+    server.succeed("curl --fail http://localhost:9001 -X POST -F \"api=upload\" -F \"dir=\" -F \"file=@testfile\" | grep \"OK\"");
+    server.succeed("rm testfile")
+    server.succeed("curl --fail http://localhost:9001 -X POST -F \"api=download\" -F \"filename=testfile\" -F \"dir=\" --output testfile");
+    server.succeed("shasum testfile >> checksums && shasum --check checksums")
+  '';
+})
diff --git a/nixos/tests/incus/container.nix b/nixos/tests/incus/container.nix
index 10262cf2132b8..d2b8037cae707 100644
--- a/nixos/tests/incus/container.nix
+++ b/nixos/tests/incus/container.nix
@@ -11,8 +11,8 @@ let
     extra;
   };
 
-  container-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
-  container-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+  container-image-metadata = releases.incusContainerMeta.${pkgs.stdenv.hostPlatform.system};
+  container-image-rootfs = releases.incusContainerImage.${pkgs.stdenv.hostPlatform.system};
 in
 {
   inherit name;
@@ -61,7 +61,7 @@ in
     machine.succeed("incus admin init --minimal")
 
     with subtest("Container image can be imported"):
-        machine.succeed("incus image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs}/*/*.tar.xz --alias nixos")
+        machine.succeed("incus image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs} --alias nixos")
 
     with subtest("Container can be launched and managed"):
         machine.succeed("incus launch nixos container")
diff --git a/nixos/tests/incus/incusd-options.nix b/nixos/tests/incus/incusd-options.nix
index 7b3a4d726e38e..afa1805b9d198 100644
--- a/nixos/tests/incus/incusd-options.nix
+++ b/nixos/tests/incus/incusd-options.nix
@@ -16,8 +16,8 @@ import ../make-test-python.nix (
       };
     };
 
-    container-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
-    container-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+    container-image-metadata = releases.incusContainerMeta.${pkgs.stdenv.hostPlatform.system};
+    container-image-rootfs = releases.incusContainerImage.${pkgs.stdenv.hostPlatform.system};
   in
   {
     name = "incusd-options";
@@ -87,7 +87,7 @@ import ../make-test-python.nix (
       machine.wait_for_unit("incus-preseed.service")
 
       with subtest("Container image can be imported"):
-          machine.succeed("incus image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs}/*/*.tar.xz --alias nixos")
+          machine.succeed("incus image import ${container-image-metadata}/*/*.tar.xz ${container-image-rootfs} --alias nixos")
 
       with subtest("Container can be launched and managed"):
           machine.succeed("incus launch nixos container")
diff --git a/nixos/tests/incus/virtual-machine.nix b/nixos/tests/incus/virtual-machine.nix
index 70e54191d3304..f5ac4c8eee1f2 100644
--- a/nixos/tests/incus/virtual-machine.nix
+++ b/nixos/tests/incus/virtual-machine.nix
@@ -11,8 +11,8 @@ let
     };
   };
 
-  vm-image-metadata = releases.lxdVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system};
-  vm-image-disk = releases.lxdVirtualMachineImage.${pkgs.stdenv.hostPlatform.system};
+  vm-image-metadata = releases.incusVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system};
+  vm-image-disk = releases.incusVirtualMachineImage.${pkgs.stdenv.hostPlatform.system};
 
   instance-name = "instance1";
 in
@@ -64,10 +64,10 @@ in
         with machine.nested("Waiting for instance to start and be usable"):
           retry(instance_is_up)
 
-    with subtest("lxd-agent is started"):
-        machine.succeed("incus exec ${instance-name} systemctl is-active lxd-agent")
+    with subtest("incus-agent is started"):
+        machine.succeed("incus exec ${instance-name} systemctl is-active incus-agent")
 
-    with subtest("lxd-agent has a valid path"):
+    with subtest("incus-agent has a valid path"):
         machine.succeed("incus exec ${instance-name} -- bash -c 'true'")
 
     with subtest("guest supports cpu hotplug"):
diff --git a/nixos/tests/installed-tests/flatpak-builder.nix b/nixos/tests/installed-tests/flatpak-builder.nix
index d5e04fcf975ce..fd3df6bdea8f5 100644
--- a/nixos/tests/installed-tests/flatpak-builder.nix
+++ b/nixos/tests/installed-tests/flatpak-builder.nix
@@ -5,8 +5,11 @@ makeInstalledTest {
 
   testConfig = {
     services.flatpak.enable = true;
-    xdg.portal.enable = true;
-    xdg.portal.extraPortals = with pkgs; [ xdg-desktop-portal-gtk ];
+    xdg.portal = {
+      enable = true;
+      extraPortals = with pkgs; [ xdg-desktop-portal-gtk ];
+      config.common.default = "gtk";
+    };
     environment.systemPackages = with pkgs; [ flatpak-builder ] ++ flatpak-builder.installedTestsDependencies;
     virtualisation.diskSize = 2048;
   };
diff --git a/nixos/tests/installed-tests/ibus.nix b/nixos/tests/installed-tests/ibus.nix
index 028c20c29f2d6..8cae29112f98b 100644
--- a/nixos/tests/installed-tests/ibus.nix
+++ b/nixos/tests/installed-tests/ibus.nix
@@ -5,7 +5,10 @@ makeInstalledTest {
 
   testConfig = {
     i18n.supportedLocales = [ "all" ];
-    i18n.inputMethod.enabled = "ibus";
+    i18n.inputMethod = {
+      enable = true;
+      type = "ibus";
+    };
     systemd.user.services.ibus-daemon = {
       serviceConfig.ExecStart = "${pkgs.ibus}/bin/ibus-daemon --xim --verbose";
       wantedBy = [ "graphical-session.target" ];
diff --git a/nixos/tests/installed-tests/ostree.nix b/nixos/tests/installed-tests/ostree.nix
index 90e09ad4ddf49..b90870204225b 100644
--- a/nixos/tests/installed-tests/ostree.nix
+++ b/nixos/tests/installed-tests/ostree.nix
@@ -1,4 +1,4 @@
-{ pkgs, lib, makeInstalledTest, ... }:
+{ pkgs, makeInstalledTest, ... }:
 
 makeInstalledTest {
   tested = pkgs.ostree;
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index bb6ad79615fa3..d57866c9f52c6 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -11,7 +11,7 @@ let
 
   # The configuration to install.
   makeConfig = { bootLoader, grubDevice, grubIdentifier, grubUseEfi
-               , extraConfig, forceGrubReinstallCount ? 0, flake ? false
+               , extraConfig, forceGrubReinstallCount ? 0, withTestInstrumentation ? true
                , clevisTest
                }:
     pkgs.writeText "configuration.nix" ''
@@ -19,7 +19,7 @@ let
 
       { imports =
           [ ./hardware-configuration.nix
-            ${if flake
+            ${if !withTestInstrumentation
               then "" # Still included, but via installer/flake.nix
               else "<nixpkgs/nixos/modules/testing/test-instrumentation.nix>"}
           ];
@@ -81,7 +81,7 @@ let
   # partitions and filesystems.
   testScriptFun = { bootLoader, createPartitions, grubDevice, grubUseEfi, grubIdentifier
                   , postInstallCommands, postBootCommands, extraConfig
-                  , testSpecialisationConfig, testFlakeSwitch, clevisTest, clevisFallbackTest
+                  , testSpecialisationConfig, testFlakeSwitch, testByAttrSwitch, clevisTest, clevisFallbackTest
                   , disableFileSystems
                   }:
     let
@@ -316,6 +316,119 @@ let
 
       target.shutdown()
     ''
+    + optionalString testByAttrSwitch ''
+      with subtest("Configure system with attribute set"):
+        target.succeed("""
+          mkdir /root/my-config
+          mv /etc/nixos/hardware-configuration.nix /root/my-config/
+          rm /etc/nixos/configuration.nix
+        """)
+        target.copy_from_host_via_shell(
+          "${makeConfig {
+               inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
+               forceGrubReinstallCount = 1;
+               withTestInstrumentation = false;
+            }}",
+          "/root/my-config/configuration.nix",
+        )
+        target.copy_from_host_via_shell(
+          "${./installer/byAttrWithChannel.nix}",
+          "/root/my-config/default.nix",
+        )
+      with subtest("Switch to attribute set based config with channels"):
+        target.succeed("nixos-rebuild switch --file /root/my-config/default.nix")
+
+      target.shutdown()
+
+      ${startTarget}
+
+      target.succeed("""
+        rm /root/my-config/default.nix
+      """)
+      target.copy_from_host_via_shell(
+        "${./installer/byAttrNoChannel.nix}",
+        "/root/my-config/default.nix",
+      )
+
+      target.succeed("""
+        pkgs=$(readlink -f /nix/var/nix/profiles/per-user/root/channels)/nixos
+        if ! [[ -e $pkgs/pkgs/top-level/default.nix ]]; then
+          echo 1>&2 "$pkgs does not seem to be a nixpkgs source. Please fix the test so that pkgs points to a nixpkgs source.";
+          exit 1;
+        fi
+        sed -e s^@nixpkgs@^$pkgs^ -i /root/my-config/default.nix
+
+      """)
+
+      with subtest("Switch to attribute set based config without channels"):
+        target.succeed("nixos-rebuild switch --file /root/my-config/default.nix")
+
+      target.shutdown()
+
+      ${startTarget}
+
+      with subtest("nix-channel command is not available anymore"):
+        target.succeed("! which nix-channel")
+
+      with subtest("builtins.nixPath is now empty"):
+        target.succeed("""
+          [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]]
+        """)
+
+      with subtest("<nixpkgs> does not resolve"):
+        target.succeed("""
+          ! nix-instantiate '<nixpkgs>' --eval --expr
+        """)
+
+      with subtest("Evaluate attribute set based config in fresh env without nix-channel"):
+        target.succeed("nixos-rebuild switch --file /root/my-config/default.nix")
+
+      with subtest("Evaluate attribute set based config in fresh env without channel profiles"):
+        target.succeed("""
+          (
+            exec 1>&2
+            mkdir -p /root/restore
+            mv -v /root/.nix-channels /root/restore/
+            mv -v ~/.nix-defexpr /root/restore/
+            mkdir -p /root/restore/channels
+            mv -v /nix/var/nix/profiles/per-user/root/channels* /root/restore/channels/
+          )
+        """)
+        target.succeed("nixos-rebuild switch --file /root/my-config/default.nix")
+    ''
+    + optionalString (testByAttrSwitch && testFlakeSwitch) ''
+      with subtest("Restore channel profiles"):
+        target.succeed("""
+          (
+            exec 1>&2
+            mv -v /root/restore/.nix-channels /root/
+            mv -v /root/restore/.nix-defexpr ~/.nix-defexpr
+            mv -v /root/restore/channels/* /nix/var/nix/profiles/per-user/root/
+            rm -vrf /root/restore
+          )
+        """)
+
+      with subtest("Restore /etc/nixos"):
+        target.succeed("""
+          mv -v /root/my-config/hardware-configuration.nix /etc/nixos/
+        """)
+        target.copy_from_host_via_shell(
+          "${makeConfig {
+               inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
+               forceGrubReinstallCount = 1;
+            }}",
+          "/etc/nixos/configuration.nix",
+        )
+
+      with subtest("Restore /root/my-config"):
+        target.succeed("""
+          rm -vrf /root/my-config
+        """)
+
+    ''
+    + optionalString (testByAttrSwitch && !testFlakeSwitch) ''
+      target.shutdown()
+    ''
     + optionalString testFlakeSwitch ''
       ${startTarget}
 
@@ -330,7 +443,7 @@ let
           "${makeConfig {
                inherit bootLoader grubDevice grubIdentifier grubUseEfi extraConfig clevisTest;
                forceGrubReinstallCount = 1;
-               flake = true;
+               withTestInstrumentation = false;
             }}",
           "/root/my-config/configuration.nix",
         )
@@ -350,7 +463,32 @@ let
         """)
 
       with subtest("Switch to flake based config"):
-        target.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
+        target.succeed("nixos-rebuild switch --flake /root/my-config#xyz 2>&1 | tee activation-log >&2")
+
+        target.succeed("""
+          cat -n activation-log >&2
+        """)
+
+        target.succeed("""
+          grep -F '/root/.nix-defexpr/channels exists, but channels have been disabled.' activation-log
+        """)
+        target.succeed("""
+          grep -F '/nix/var/nix/profiles/per-user/root/channels exists, but channels have been disabled.' activation-log
+        """)
+        target.succeed("""
+          grep -F '/root/.nix-defexpr/channels exists, but channels have been disabled.' activation-log
+        """)
+        target.succeed("""
+          grep -F 'Due to https://github.com/NixOS/nix/issues/9574, Nix may still use these channels when NIX_PATH is unset.' activation-log
+        """)
+        target.succeed("rm activation-log")
+
+        # Perform the suggested cleanups we've just seen in the log
+        # TODO after https://github.com/NixOS/nix/issues/9574: don't remove them yet
+        target.succeed("""
+          rm -rf /root/.nix-defexpr/channels /nix/var/nix/profiles/per-user/root/channels /root/.nix-defexpr/channels
+        """)
+
 
       target.shutdown()
 
@@ -361,10 +499,20 @@ let
 
       # Note that the channel profile is still present on disk, but configured
       # not to be used.
-      with subtest("builtins.nixPath is now empty"):
-        target.succeed("""
-          [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]]
-        """)
+      # TODO after issue https://github.com/NixOS/nix/issues/9574: re-enable this assertion
+      # I believe what happens is
+      #   - because of the issue, we've removed the `nix-path =` line from nix.conf
+      #   - the "backdoor" shell is not a proper session and does not have `NIX_PATH=""` set
+      #   - seeing no nix path settings at all, Nix loads its hardcoded default value,
+      #     which is unfortunately non-empty
+      # Or maybe it's the new default NIX_PATH?? :(
+      # with subtest("builtins.nixPath is now empty"):
+      #   target.succeed("""
+      #     (
+      #       set -x;
+      #       [[ "[ ]" == "$(nix-instantiate builtins.nixPath --eval --expr)" ]];
+      #     )
+      #   """)
 
       with subtest("<nixpkgs> does not resolve"):
         target.succeed("""
@@ -378,12 +526,16 @@ let
         target.succeed("""
           (
             exec 1>&2
-            rm -v /root/.nix-channels
+            rm -vf /root/.nix-channels
             rm -vrf ~/.nix-defexpr
             rm -vrf /nix/var/nix/profiles/per-user/root/channels*
           )
         """)
-        target.succeed("nixos-rebuild switch --flake /root/my-config#xyz")
+        target.succeed("nixos-rebuild switch --flake /root/my-config#xyz | tee activation-log >&2")
+        target.succeed("cat -n activation-log >&2")
+        target.succeed("! grep -F '/root/.nix-defexpr/channels' activation-log")
+        target.succeed("! grep -F 'but channels have been disabled' activation-log")
+        target.succeed("! grep -F 'https://github.com/NixOS/nix/issues/9574' activation-log")
 
       target.shutdown()
     '';
@@ -399,6 +551,7 @@ let
     , enableOCR ? false, meta ? {}
     , testSpecialisationConfig ? false
     , testFlakeSwitch ? false
+    , testByAttrSwitch ? false
     , clevisTest ? false
     , clevisFallbackTest ? false
     , disableFileSystems ? false
@@ -533,7 +686,7 @@ let
       testScript = testScriptFun {
         inherit bootLoader createPartitions postInstallCommands postBootCommands
                 grubDevice grubIdentifier grubUseEfi extraConfig
-                testSpecialisationConfig testFlakeSwitch clevisTest clevisFallbackTest
+                testSpecialisationConfig testFlakeSwitch testByAttrSwitch clevisTest clevisFallbackTest
                 disableFileSystems;
       };
     };
@@ -589,6 +742,15 @@ let
     testFlakeSwitch = true;
   };
 
+  simple-test-config-by-attr = simple-test-config // {
+    testByAttrSwitch = true;
+  };
+
+  simple-test-config-from-by-attr-to-flake = simple-test-config // {
+    testByAttrSwitch = true;
+    testFlakeSwitch = true;
+  };
+
   simple-uefi-grub-config = {
     createPartitions = ''
       installer.succeed(
@@ -782,6 +944,10 @@ in {
 
   switchToFlake = makeInstallerTest "switch-to-flake" simple-test-config-flake;
 
+  switchToByAttr = makeInstallerTest "switch-to-by-attr" simple-test-config-by-attr;
+
+  switchFromByAttrToFlake = makeInstallerTest "switch-from-by-attr-to-flake" simple-test-config-from-by-attr-to-flake;
+
   # Test cloned configurations with the simple grub configuration
   simpleSpecialised = makeInstallerTest "simpleSpecialised" (simple-test-config // specialisation-test-extraconfig);
 
diff --git a/nixos/tests/installer/byAttrNoChannel.nix b/nixos/tests/installer/byAttrNoChannel.nix
new file mode 100644
index 0000000000000..03293cd4a0e35
--- /dev/null
+++ b/nixos/tests/installer/byAttrNoChannel.nix
@@ -0,0 +1,18 @@
+# This file gets copied into the installation
+
+let
+  nixpkgs = "@nixpkgs@";
+in
+
+{ evalConfig ? import "${nixpkgs}/nixos/lib/eval-config.nix" }:
+
+evalConfig {
+  modules = [
+    ./configuration.nix
+    ( import "${nixpkgs}/nixos/modules/testing/test-instrumentation.nix" )
+    {
+      # Disable nix channels
+      nix.channel.enable = false;
+    }
+  ];
+}
diff --git a/nixos/tests/installer/byAttrWithChannel.nix b/nixos/tests/installer/byAttrWithChannel.nix
new file mode 100644
index 0000000000000..951231dcba3e7
--- /dev/null
+++ b/nixos/tests/installer/byAttrWithChannel.nix
@@ -0,0 +1,10 @@
+# This file gets copied into the installation
+
+{ evalConfig ? import <nixpkgs/nixos/lib/eval-config.nix> }:
+
+evalConfig {
+  modules = [
+    ./configuration.nix
+    ( import <nixpkgs/nixos/modules/testing/test-instrumentation.nix> )
+  ];
+}
diff --git a/nixos/tests/ipv6.nix b/nixos/tests/ipv6.nix
index 7f91457fa5ea8..8fa7eec8ffb2a 100644
--- a/nixos/tests/ipv6.nix
+++ b/nixos/tests/ipv6.nix
@@ -39,6 +39,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
         { services.httpd.enable = true;
           services.httpd.adminAddr = "foo@example.org";
           networking.firewall.allowedTCPPorts = [ 80 ];
+          # disable testing driver's default IPv6 address.
+          networking.interfaces.eth1.ipv6.addresses = lib.mkForce [ ];
         };
 
       router =
diff --git a/nixos/tests/iscsi-root.nix b/nixos/tests/iscsi-root.nix
index 0d7c48464eecc..6953b6ce9a065 100644
--- a/nixos/tests/iscsi-root.nix
+++ b/nixos/tests/iscsi-root.nix
@@ -59,7 +59,7 @@ import ./make-test-python.nix (
                         ];
                         portals = [
                           {
-                            ip_address = "0.0.0.0";
+                            ip_address = "[::]";
                             iser = false;
                             offload = false;
                             port = 3260;
@@ -93,7 +93,7 @@ import ./make-test-python.nix (
               xfsprogs
             ];
 
-            system.extraDependencies = [ nodes.initiatorRootDisk.config.system.build.toplevel ];
+            system.extraDependencies = [ nodes.initiatorRootDisk.system.build.toplevel ];
 
             nix.settings = {
               substituters = lib.mkForce [];
@@ -108,7 +108,7 @@ import ./make-test-python.nix (
               [
                 "boot.shell_on_fail"
                 "console=tty1"
-                "ip=${config.networking.primaryIPAddress}:::255.255.255.0::ens9:none"
+                "ip=${config.networking.primaryIPAddress}:::255.255.255.0::eth1:none"
               ]
             );
 
diff --git a/nixos/tests/jackett.nix b/nixos/tests/jackett.nix
index bc8b724e8b4b6..4e65cb61d17a7 100644
--- a/nixos/tests/jackett.nix
+++ b/nixos/tests/jackett.nix
@@ -1,17 +1,21 @@
 import ./make-test-python.nix ({ lib, ... }:
 
-{
+let
+  jackettPort = 9117;
+in {
   name = "jackett";
   meta.maintainers = with lib.maintainers; [ etu ];
 
   nodes.machine =
-    { pkgs, ... }:
-    { services.jackett.enable = true; };
+    { pkgs, ... }: {
+      services.jackett.enable = true;
+      services.jackett.port = jackettPort;
+    };
 
   testScript = ''
     machine.start()
     machine.wait_for_unit("jackett.service")
-    machine.wait_for_open_port(9117)
-    machine.succeed("curl --fail http://localhost:9117/")
+    machine.wait_for_open_port(${toString jackettPort})
+    machine.succeed("curl --fail http://localhost:${toString jackettPort}/")
   '';
 })
diff --git a/nixos/tests/jool.nix b/nixos/tests/jool.nix
index 93575f07b1c8c..37a4ad6ce0111 100644
--- a/nixos/tests/jool.nix
+++ b/nixos/tests/jool.nix
@@ -165,9 +165,12 @@ in
       virtualisation.vlans = [ 1 ];
 
       networking.interfaces.eth1.ipv6 = {
-        addresses = [ { address = "2001:db8::8"; prefixLength = 96; } ];
-        routes    = [ { address = "64:ff9b::";   prefixLength = 96;
-                        via = "2001:db8::1"; } ];
+        addresses = lib.mkForce [ { address = "2001:db8::8"; prefixLength = 96; } ];
+        routes = lib.mkForce [ {
+          address = "64:ff9b::";
+          prefixLength = 96;
+          via = "2001:db8::1";
+        } ];
       };
     };
 
@@ -177,9 +180,12 @@ in
 
       virtualisation.vlans = [ 1 ];
       networking.interfaces.eth1.ipv6 = {
-        addresses = [ { address = "2001:db8::9"; prefixLength = 96; } ];
-        routes    = [ { address = "64:ff9b::";   prefixLength = 96;
-                        via = "2001:db8::1"; } ];
+        addresses = lib.mkForce [ { address = "2001:db8::9"; prefixLength = 96; } ];
+        routes    = lib.mkForce [ {
+          address = "64:ff9b::";
+          prefixLength = 96;
+          via = "2001:db8::1";
+        } ];
       };
     };
 
diff --git a/nixos/tests/k3s/airgap-images.nix b/nixos/tests/k3s/airgap-images.nix
new file mode 100644
index 0000000000000..1dbe8890fe734
--- /dev/null
+++ b/nixos/tests/k3s/airgap-images.nix
@@ -0,0 +1,42 @@
+# A test that imports k3s airgapped images and verifies that all expected images are present
+import ../make-test-python.nix (
+  { lib, k3s, ... }:
+  {
+    name = "${k3s.name}-airgap-images";
+    meta.maintainers = lib.teams.k3s.members;
+
+    nodes.machine = _: {
+      # k3s uses enough resources the default vm fails.
+      virtualisation.memorySize = 1536;
+      virtualisation.diskSize = 4096;
+
+      services.k3s = {
+        enable = true;
+        role = "server";
+        package = k3s;
+        # Slightly reduce resource usage
+        extraFlags = [
+          "--disable coredns"
+          "--disable local-storage"
+          "--disable metrics-server"
+          "--disable servicelb"
+          "--disable traefik"
+        ];
+        images = [ k3s.airgapImages ];
+      };
+    };
+
+    testScript = ''
+      import json
+
+      start_all()
+      machine.wait_for_unit("k3s")
+      machine.wait_until_succeeds("journalctl -r --no-pager -u k3s | grep \"Imported images from /var/lib/rancher/k3s/agent/images/\"", timeout=60)
+      images = json.loads(machine.succeed("crictl img -o json"))
+      image_names = [i["repoTags"][0] for i in images["images"]]
+      with open("${k3s.imagesList}") as expected_images:
+        for line in expected_images:
+          assert line.rstrip() in image_names, f"The image {line.rstrip()} is not present in the airgap images archive"
+    '';
+  }
+)
diff --git a/nixos/tests/k3s/auto-deploy.nix b/nixos/tests/k3s/auto-deploy.nix
new file mode 100644
index 0000000000000..c25503ac10874
--- /dev/null
+++ b/nixos/tests/k3s/auto-deploy.nix
@@ -0,0 +1,125 @@
+# Tests whether container images are imported and auto deploying manifests work
+import ../make-test-python.nix (
+  {
+    pkgs,
+    lib,
+    k3s,
+    ...
+  }:
+  let
+    pauseImageEnv = pkgs.buildEnv {
+      name = "k3s-pause-image-env";
+      paths = with pkgs; [
+        tini
+        (hiPrio coreutils)
+        busybox
+      ];
+    };
+    pauseImage = pkgs.dockerTools.buildImage {
+      name = "test.local/pause";
+      tag = "local";
+      copyToRoot = pauseImageEnv;
+      config.Entrypoint = [
+        "/bin/tini"
+        "--"
+        "/bin/sleep"
+        "inf"
+      ];
+    };
+    helloImage = pkgs.dockerTools.buildImage {
+      name = "test.local/hello";
+      tag = "local";
+      copyToRoot = pkgs.hello;
+      config.Entrypoint = [ "${pkgs.hello}/bin/hello" ];
+    };
+  in
+  {
+    name = "${k3s.name}-auto-deploy";
+
+    nodes.machine =
+      { pkgs, ... }:
+      {
+        environment.systemPackages = [ k3s ];
+
+        # k3s uses enough resources the default vm fails.
+        virtualisation.memorySize = 1536;
+        virtualisation.diskSize = 4096;
+
+        services.k3s.enable = true;
+        services.k3s.role = "server";
+        services.k3s.package = k3s;
+        # Slightly reduce resource usage
+        services.k3s.extraFlags = [
+          "--disable coredns"
+          "--disable local-storage"
+          "--disable metrics-server"
+          "--disable servicelb"
+          "--disable traefik"
+          "--pause-image test.local/pause:local"
+        ];
+        services.k3s.images = [
+          pauseImage
+          helloImage
+        ];
+        services.k3s.manifests = {
+          absent = {
+            enable = false;
+            content = {
+              apiVersion = "v1";
+              kind = "Namespace";
+              metadata.name = "absent";
+            };
+          };
+
+          present = {
+            target = "foo-namespace.yaml";
+            content = {
+              apiVersion = "v1";
+              kind = "Namespace";
+              metadata.name = "foo";
+            };
+          };
+
+          hello.content = {
+            apiVersion = "batch/v1";
+            kind = "Job";
+            metadata.name = "hello";
+            spec = {
+              template.spec = {
+                containers = [
+                  {
+                    name = "hello";
+                    image = "test.local/hello:local";
+                  }
+                ];
+                restartPolicy = "OnFailure";
+              };
+            };
+          };
+        };
+      };
+
+    testScript = ''
+      start_all()
+
+      machine.wait_for_unit("k3s")
+      # check existence of the manifest files
+      machine.fail("ls /var/lib/rancher/k3s/server/manifests/absent.yaml")
+      machine.succeed("ls /var/lib/rancher/k3s/server/manifests/foo-namespace.yaml")
+      machine.succeed("ls /var/lib/rancher/k3s/server/manifests/hello.yaml")
+
+      # check if container images got imported
+      machine.wait_until_succeeds("crictl img | grep 'test\.local/pause'")
+      machine.wait_until_succeeds("crictl img | grep 'test\.local/hello'")
+
+      # check if resources of manifests got created
+      machine.wait_until_succeeds("kubectl get ns foo")
+      machine.wait_until_succeeds("kubectl wait --for=condition=complete job/hello")
+      machine.fail("kubectl get ns absent")
+
+      machine.shutdown()
+    '';
+
+    meta.maintainers = lib.teams.k3s.members;
+  }
+)
diff --git a/nixos/tests/k3s/containerd-config.nix b/nixos/tests/k3s/containerd-config.nix
new file mode 100644
index 0000000000000..db4b3320411f0
--- /dev/null
+++ b/nixos/tests/k3s/containerd-config.nix
@@ -0,0 +1,52 @@
+# A test that containerdConfigTemplate settings get written to containerd/config.toml
+import ../make-test-python.nix (
+  { lib, k3s, ... }:
+  let
+    nodeName = "test";
+  in
+  {
+    name = "${k3s.name}-containerd-config";
+    nodes.machine =
+      { ... }:
+      {
+        # k3s uses enough resources the default vm fails.
+        virtualisation.memorySize = 1536;
+        virtualisation.diskSize = 4096;
+
+        services.k3s = {
+          enable = true;
+          package = k3s;
+          # Slightly reduce resource usage
+          extraFlags = [
+            "--disable coredns"
+            "--disable local-storage"
+            "--disable metrics-server"
+            "--disable servicelb"
+            "--disable traefik"
+            "--node-name ${nodeName}"
+          ];
+          containerdConfigTemplate = ''
+            # Base K3s config
+            {{ template "base" . }}
+
+            # MAGIC COMMENT
+          '';
+        };
+      };
+
+    testScript = ''
+      start_all()
+      machine.wait_for_unit("k3s")
+      # wait until the node is ready
+      machine.wait_until_succeeds(r"""kubectl wait --for='jsonpath={.status.conditions[?(@.type=="Ready")].status}=True' nodes/${nodeName}""")
+      # test whether the config template file contains the magic comment
+      out=machine.succeed("cat /var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl")
+      assert "MAGIC COMMENT" in out, "the containerd config template does not contain the magic comment"
+      # test whether the config file contains the magic comment
+      out=machine.succeed("cat /var/lib/rancher/k3s/agent/etc/containerd/config.toml")
+      assert "MAGIC COMMENT" in out, "the containerd config does not contain the magic comment"
+    '';
+
+    meta.maintainers = lib.teams.k3s.members;
+  }
+)
diff --git a/nixos/tests/k3s/default.nix b/nixos/tests/k3s/default.nix
index 297b05a4e4a74..7edaf6f38ed28 100644
--- a/nixos/tests/k3s/default.nix
+++ b/nixos/tests/k3s/default.nix
@@ -7,7 +7,13 @@ let
   allK3s = lib.filterAttrs (n: _: lib.strings.hasPrefix "k3s_" n) pkgs;
 in
 {
-  # Testing K3s with Etcd backend
+  airgap-images = lib.mapAttrs (
+    _: k3s: import ./airgap-images.nix { inherit system pkgs k3s; }
+  ) allK3s;
+  auto-deploy = lib.mapAttrs (_: k3s: import ./auto-deploy.nix { inherit system pkgs k3s; }) allK3s;
+  containerd-config = lib.mapAttrs (
+    _: k3s: import ./containerd-config.nix { inherit system pkgs k3s; }
+  ) allK3s;
   etcd = lib.mapAttrs (
     _: k3s:
     import ./etcd.nix {
@@ -15,8 +21,9 @@ in
       inherit (pkgs) etcd;
     }
   ) allK3s;
-  # Run a single node k3s cluster and verify a pod can run
-  single-node = lib.mapAttrs (_: k3s: import ./single-node.nix { inherit system pkgs k3s; }) allK3s;
-  # Run a multi-node k3s cluster and verify pod networking works across nodes
+  kubelet-config = lib.mapAttrs (
+    _: k3s: import ./kubelet-config.nix { inherit system pkgs k3s; }
+  ) allK3s;
   multi-node = lib.mapAttrs (_: k3s: import ./multi-node.nix { inherit system pkgs k3s; }) allK3s;
+  single-node = lib.mapAttrs (_: k3s: import ./single-node.nix { inherit system pkgs k3s; }) allK3s;
 }
diff --git a/nixos/tests/k3s/etcd.nix b/nixos/tests/k3s/etcd.nix
index ac0aa90472516..2616ab02a6092 100644
--- a/nixos/tests/k3s/etcd.nix
+++ b/nixos/tests/k3s/etcd.nix
@@ -1,3 +1,4 @@
+# Tests K3s with Etcd backend
 import ../make-test-python.nix (
   {
     pkgs,
@@ -49,20 +50,14 @@ import ../make-test-python.nix (
           services.k3s = {
             enable = true;
             role = "server";
-            extraFlags = builtins.toString [
+            extraFlags = [
               "--datastore-endpoint=\"http://192.168.1.1:2379\""
-              "--disable"
-              "coredns"
-              "--disable"
-              "local-storage"
-              "--disable"
-              "metrics-server"
-              "--disable"
-              "servicelb"
-              "--disable"
-              "traefik"
-              "--node-ip"
-              "192.168.1.2"
+              "--disable coredns"
+              "--disable local-storage"
+              "--disable metrics-server"
+              "--disable servicelb"
+              "--disable traefik"
+              "--node-ip 192.168.1.2"
             ];
           };
 
@@ -125,6 +120,6 @@ import ../make-test-python.nix (
           etcd.shutdown()
     '';
 
-    meta.maintainers = etcd.meta.maintainers ++ k3s.meta.maintainers;
+    meta.maintainers = etcd.meta.maintainers ++ lib.teams.k3s.members;
   }
 )
diff --git a/nixos/tests/k3s/kubelet-config.nix b/nixos/tests/k3s/kubelet-config.nix
new file mode 100644
index 0000000000000..e2c8e0434d629
--- /dev/null
+++ b/nixos/tests/k3s/kubelet-config.nix
@@ -0,0 +1,80 @@
+# A test that sets extra kubelet configuration and enables graceful node shutdown
+import ../make-test-python.nix (
+  {
+    pkgs,
+    lib,
+    k3s,
+    ...
+  }:
+  let
+    nodeName = "test";
+    shutdownGracePeriod = "1m13s";
+    shutdownGracePeriodCriticalPods = "13s";
+    podsPerCore = 3;
+    memoryThrottlingFactor = 0.69;
+    containerLogMaxSize = "5Mi";
+  in
+  {
+    name = "${k3s.name}-kubelet-config";
+    nodes.machine =
+      { pkgs, ... }:
+      {
+        environment.systemPackages = [ pkgs.jq ];
+
+        # k3s uses enough resources the default vm fails.
+        virtualisation.memorySize = 1536;
+        virtualisation.diskSize = 4096;
+
+        services.k3s = {
+          enable = true;
+          package = k3s;
+          # Slightly reduce resource usage
+          extraFlags = [
+            "--disable coredns"
+            "--disable local-storage"
+            "--disable metrics-server"
+            "--disable servicelb"
+            "--disable traefik"
+            "--node-name ${nodeName}"
+          ];
+          gracefulNodeShutdown = {
+            enable = true;
+            inherit shutdownGracePeriod shutdownGracePeriodCriticalPods;
+          };
+          extraKubeletConfig = {
+            inherit podsPerCore memoryThrottlingFactor containerLogMaxSize;
+          };
+        };
+      };
+
+    testScript = ''
+      import json
+
+      start_all()
+      machine.wait_for_unit("k3s")
+      # wait until the node is ready
+      machine.wait_until_succeeds(r"""kubectl wait --for='jsonpath={.status.conditions[?(@.type=="Ready")].status}=True' nodes/${nodeName}""")
+      # test whether the kubelet registered an inhibitor lock
+      machine.succeed("systemd-inhibit --list --no-legend | grep \"kubelet.*k3s-server.*shutdown\"")
+      # run kubectl proxy in the background, close stdout through redirection to not wait for the command to finish
+      machine.execute("kubectl proxy --address 127.0.0.1 --port=8001 >&2 &")
+      machine.wait_until_succeeds("nc -z 127.0.0.1 8001")
+      # get the kubeletconfig
+      kubelet_config=json.loads(machine.succeed("curl http://127.0.0.1:8001/api/v1/nodes/${nodeName}/proxy/configz | jq '.kubeletconfig'"))
+
+      with subtest("Kubelet config values are set correctly"):
+        assert kubelet_config["shutdownGracePeriod"] == "${shutdownGracePeriod}", \
+          f"unexpected value for shutdownGracePeriod: {kubelet_config["shutdownGracePeriod"]}"
+        assert kubelet_config["shutdownGracePeriodCriticalPods"] == "${shutdownGracePeriodCriticalPods}", \
+          f"unexpected value for shutdownGracePeriodCriticalPods: {kubelet_config["shutdownGracePeriodCriticalPods"]}"
+        assert kubelet_config["podsPerCore"] == ${toString podsPerCore}, \
+          f"unexpected value for podsPerCore: {kubelet_config["podsPerCore"]}"
+        assert kubelet_config["memoryThrottlingFactor"] == ${toString memoryThrottlingFactor}, \
+          f"unexpected value for memoryThrottlingFactor: {kubelet_config["memoryThrottlingFactor"]}"
+        assert kubelet_config["containerLogMaxSize"] == "${containerLogMaxSize}", \
+          f"unexpected value for containerLogMaxSize: {kubelet_config["containerLogMaxSize"]}"
+    '';
+
+    meta.maintainers = lib.teams.k3s.members;
+  }
+)
diff --git a/nixos/tests/k3s/multi-node.nix b/nixos/tests/k3s/multi-node.nix
index b618d2aff34c4..5fe106453f308 100644
--- a/nixos/tests/k3s/multi-node.nix
+++ b/nixos/tests/k3s/multi-node.nix
@@ -1,3 +1,4 @@
+# A test that runs a multi-node k3s cluster and verify pod networking works across nodes
 import ../make-test-python.nix (
   {
     pkgs,
@@ -75,21 +76,14 @@ import ../make-test-python.nix (
             role = "server";
             package = k3s;
             clusterInit = true;
-            extraFlags = builtins.toString [
-              "--disable"
-              "coredns"
-              "--disable"
-              "local-storage"
-              "--disable"
-              "metrics-server"
-              "--disable"
-              "servicelb"
-              "--disable"
-              "traefik"
-              "--node-ip"
-              "192.168.1.1"
-              "--pause-image"
-              "test.local/pause:local"
+            extraFlags = [
+              "--disable coredns"
+              "--disable local-storage"
+              "--disable metrics-server"
+              "--disable servicelb"
+              "--disable traefik"
+              "--node-ip 192.168.1.1"
+              "--pause-image test.local/pause:local"
             ];
           };
           networking.firewall.allowedTCPPorts = [
@@ -189,8 +183,6 @@ import ../make-test-python.nix (
         };
     };
 
-    meta.maintainers = k3s.meta.maintainers;
-
     testScript = ''
       machines = [server, server2, agent]
       for m in machines:
@@ -239,5 +231,7 @@ import ../make-test-python.nix (
       for m in machines:
           m.shutdown()
     '';
+
+    meta.maintainers = lib.teams.k3s.members;
   }
 )
diff --git a/nixos/tests/k3s/single-node.nix b/nixos/tests/k3s/single-node.nix
index 80d80a55ddf41..145bce3242999 100644
--- a/nixos/tests/k3s/single-node.nix
+++ b/nixos/tests/k3s/single-node.nix
@@ -1,3 +1,4 @@
+# A test that runs a single node k3s cluster and verify a pod can run
 import ../make-test-python.nix (
   {
     pkgs,
@@ -40,7 +41,6 @@ import ../make-test-python.nix (
   in
   {
     name = "${k3s.name}-single-node";
-    meta.maintainers = k3s.meta.maintainers;
 
     nodes.machine =
       { pkgs, ... }:
@@ -58,19 +58,13 @@ import ../make-test-python.nix (
         services.k3s.role = "server";
         services.k3s.package = k3s;
         # Slightly reduce resource usage
-        services.k3s.extraFlags = builtins.toString [
-          "--disable"
-          "coredns"
-          "--disable"
-          "local-storage"
-          "--disable"
-          "metrics-server"
-          "--disable"
-          "servicelb"
-          "--disable"
-          "traefik"
-          "--pause-image"
-          "test.local/pause:local"
+        services.k3s.extraFlags = [
+          "--disable coredns"
+          "--disable local-storage"
+          "--disable metrics-server"
+          "--disable servicelb"
+          "--disable traefik"
+          "--pause-image test.local/pause:local"
         ];
 
         users.users = {
@@ -120,5 +114,7 @@ import ../make-test-python.nix (
 
         machine.shutdown()
       '';
+
+    meta.maintainers = lib.teams.k3s.members;
   }
 )
diff --git a/nixos/tests/kernel-generic.nix b/nixos/tests/kernel-generic.nix
index 6a8633808702f..f26038c443e21 100644
--- a/nixos/tests/kernel-generic.nix
+++ b/nixos/tests/kernel-generic.nix
@@ -23,7 +23,7 @@ let
         assert "${linuxPackages.kernel.modDirVersion}" in machine.succeed("uname -a")
       '';
   }) args);
-  kernels = (removeAttrs pkgs.linuxKernel.vanillaPackages ["__attrsFailEvaluation"]) // {
+  kernels = pkgs.linuxKernel.vanillaPackages // {
     inherit (pkgs.linuxKernel.packages)
       linux_4_19_hardened
       linux_5_4_hardened
@@ -47,6 +47,9 @@ in mapAttrs (_: lP: testsForLinuxPackages lP) kernels // {
   passthru = {
     inherit testsForLinuxPackages;
 
+    # Useful for development testing of all Kernel configs without building full Kernel
+    configfiles = mapAttrs (_: lP: lP.kernel.configfile) kernels;
+
     testsForKernel = kernel: testsForLinuxPackages (pkgs.linuxPackagesFor kernel);
   };
 }
diff --git a/nixos/tests/keycloak.nix b/nixos/tests/keycloak.nix
index 259f1340a22d7..67b412c80961d 100644
--- a/nixos/tests/keycloak.nix
+++ b/nixos/tests/keycloak.nix
@@ -44,6 +44,7 @@ let
             };
             plugins = with config.services.keycloak.package.plugins; [
               keycloak-discord
+              keycloak-metrics-spi
             ];
           };
           environment.systemPackages = with pkgs; [
@@ -121,6 +122,14 @@ let
                    | jq -r '"Authorization: bearer " + .access_token' >admin_auth_header
           """)
 
+          # Register the metrics SPI
+          keycloak.succeed(
+              """${pkgs.jre}/bin/keytool -import -alias snakeoil -file ${certs.ca.cert} -storepass aaaaaa -keystore cacert.jks -noprompt""",
+              """KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh config credentials --server '${frontendUrl}' --realm master --user admin --password "$(<${adminPasswordFile})" """,
+              """KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh update events/config -s 'eventsEnabled=true' -s 'adminEventsEnabled=true' -s 'eventsListeners+=metrics-listener'""",
+              """curl -sSf '${frontendUrl}/realms/master/metrics' | grep '^keycloak_admin_event_UPDATE'"""
+          )
+
           # Publish the realm, including a test OIDC client and user
           keycloak.succeed(
               "curl -sSf -H @admin_auth_header -X POST -H 'Content-Type: application/json' -d @${realmDataJson} '${frontendUrl}/admin/realms/'"
diff --git a/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix
index 13a2bc03831de..c75610723ea4c 100644
--- a/nixos/tests/kubernetes/base.nix
+++ b/nixos/tests/kubernetes/base.nix
@@ -59,6 +59,10 @@ let
                   securePort = 443;
                   advertiseAddress = master.ip;
                 };
+                # NOTE: what featureGates are useful for testing might change in
+                # the future, see link below to find new ones
+                # https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/
+                featureGates = {CPUManager = true; AppArmor= false;};
                 masterAddress = "${masterName}.${config.networking.domain}";
               };
             }
diff --git a/nixos/tests/libreddit.nix b/nixos/tests/libreddit.nix
deleted file mode 100644
index ecf347b9e12e4..0000000000000
--- a/nixos/tests/libreddit.nix
+++ /dev/null
@@ -1,19 +0,0 @@
-import ./make-test-python.nix ({ lib, ... }:
-
-{
-  name = "libreddit";
-  meta.maintainers = with lib.maintainers; [ fab ];
-
-  nodes.machine = {
-    services.libreddit.enable = true;
-    # Test CAP_NET_BIND_SERVICE
-    services.libreddit.port = 80;
-  };
-
-  testScript = ''
-    machine.wait_for_unit("libreddit.service")
-    machine.wait_for_open_port(80)
-    # Query a page that does not require Internet access
-    machine.succeed("curl --fail http://localhost:80/settings")
-  '';
-})
diff --git a/nixos/tests/librenms.nix b/nixos/tests/librenms.nix
index c59f56a323161..14035a01ce87d 100644
--- a/nixos/tests/librenms.nix
+++ b/nixos/tests/librenms.nix
@@ -3,7 +3,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 let
   api_token = "f87f42114e44b63ad1b9e3c3d33d6fbe"; # random md5 hash
   wrong_api_token = "e68ba041fcf1eab923a7a6de3af5f726"; # another random md5 hash
-in {
+in
+{
   name = "librenms";
   meta.maintainers = lib.teams.wdz.members;
 
@@ -49,6 +50,9 @@ in {
         API_USER_NAME=api
         API_TOKEN=${api_token} # random md5 hash
 
+        # seeding database to get the admin roles
+        ${pkgs.librenms}/artisan db:seed --force --no-interaction
+
         # we don't need to know the password, it just has to exist
         API_USER_PASS=$(${pkgs.pwgen}/bin/pwgen -s 64 1)
         ${pkgs.librenms}/artisan user:add $API_USER_NAME -r admin -p $API_USER_PASS
@@ -60,37 +64,29 @@ in {
   };
 
   nodes.snmphost = {
-    networking.firewall.allowedUDPPorts = [ 161 ];
 
-    systemd.services.snmpd = {
-      description = "snmpd";
-      after = [ "network-online.target" ];
-      wants = [ "network-online.target" ];
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        Type = "forking";
-        User = "root";
-        Group = "root";
-        ExecStart = let
-          snmpd-config = pkgs.writeText "snmpd-config" ''
-            com2sec readonly default public
-
-            group MyROGroup v2c        readonly
-            view all    included  .1                               80
-            access MyROGroup ""      any       noauth    exact  all    none   none
-
-            syslocation Testcity, Testcountry
-            syscontact Testi mc Test <test@example.com>
-          '';
-        in "${pkgs.net-snmp}/bin/snmpd -c ${snmpd-config} -C";
-      };
+    services.snmpd = {
+      enable = true;
+      openFirewall = true;
+
+      configText = ''
+        com2sec readonly default public
+
+        group MyROGroup v2c        readonly
+        view all    included  .1                               80
+        access MyROGroup ""      any       noauth    exact  all    none   none
+
+        syslocation Testcity, Testcountry
+        syscontact Testi mc Test <test@example.com>
+      '';
+
     };
   };
 
   testScript = ''
     start_all()
 
-    snmphost.wait_until_succeeds("pgrep snmpd")
+    snmphost.wait_for_unit("snmpd.service")
 
     librenms.wait_for_unit("lnms-api-init.service")
     librenms.wait_for_open_port(80)
diff --git a/nixos/tests/livebook-service.nix b/nixos/tests/livebook-service.nix
index f428412e16448..2d699efb1e3ee 100644
--- a/nixos/tests/livebook-service.nix
+++ b/nixos/tests/livebook-service.nix
@@ -11,9 +11,6 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
         enableUserService = true;
         environment = {
           LIVEBOOK_PORT = 20123;
-          LIVEBOOK_COOKIE = "chocolate chip";
-          LIVEBOOK_TOKEN_ENABLED = true;
-
         };
         environmentFile = pkgs.writeText "livebook.env" ''
           LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
@@ -38,7 +35,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
 
       machine.succeed("loginctl enable-linger alice")
       machine.wait_until_succeeds("${sudo} systemctl --user is-active livebook.service")
-      machine.wait_for_open_port(20123)
+      machine.wait_for_open_port(20123, timeout=10)
 
       machine.succeed("curl -L localhost:20123 | grep 'Type password'")
     '';
diff --git a/nixos/tests/localsend.nix b/nixos/tests/localsend.nix
new file mode 100644
index 0000000000000..8c0a6ac681900
--- /dev/null
+++ b/nixos/tests/localsend.nix
@@ -0,0 +1,21 @@
+import ./make-test-python.nix (
+  { ... }:
+  {
+    name = "localsend";
+
+    nodes.machine =
+      { ... }:
+      {
+        imports = [ ./common/x11.nix ];
+        programs.localsend.enable = true;
+      };
+
+    testScript = ''
+      machine.wait_for_x()
+      machine.succeed("localsend_app >&2 &")
+      machine.wait_for_open_port(53317)
+      machine.wait_for_window("LocalSend", 10)
+      machine.succeed("netstat --listening --program --tcp | grep -P 'tcp.*53317.*localsend'")
+    '';
+  }
+)
diff --git a/nixos/tests/login.nix b/nixos/tests/login.nix
index bcaee03175ad3..e3b1b877940ac 100644
--- a/nixos/tests/login.nix
+++ b/nixos/tests/login.nix
@@ -9,7 +9,6 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
   nodes.machine =
     { pkgs, lib, ... }:
     { boot.kernelPackages = lib.mkIf latestKernel pkgs.linuxPackages_latest;
-      sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
     };
 
   testScript = ''
diff --git a/nixos/tests/lomiri-calculator-app.nix b/nixos/tests/lomiri-calculator-app.nix
new file mode 100644
index 0000000000000..14d8073611f23
--- /dev/null
+++ b/nixos/tests/lomiri-calculator-app.nix
@@ -0,0 +1,59 @@
+{ pkgs, lib, ... }:
+{
+  name = "lomiri-calculator-app-standalone";
+  meta.maintainers = lib.teams.lomiri.members;
+
+  nodes.machine =
+    { config, pkgs, ... }:
+    {
+      imports = [ ./common/x11.nix ];
+
+      services.xserver.enable = true;
+
+      environment = {
+        systemPackages = with pkgs.lomiri; [
+          suru-icon-theme
+          lomiri-calculator-app
+        ];
+        variables = {
+          UITK_ICON_THEME = "suru";
+        };
+      };
+
+      i18n.supportedLocales = [ "all" ];
+
+      fonts.packages = with pkgs; [
+        # Intended font & helps with OCR
+        ubuntu-classic
+      ];
+    };
+
+  enableOCR = true;
+
+  testScript = ''
+    machine.wait_for_x()
+
+    with subtest("lomiri calculator launches"):
+        machine.execute("lomiri-calculator-app >&2 &")
+        machine.wait_for_text("Calculator")
+        machine.screenshot("lomiri-calculator")
+
+    with subtest("lomiri calculator works"):
+        machine.send_key("tab") # Fix focus
+
+        machine.send_chars("22*16\n")
+        machine.wait_for_text("352")
+        machine.screenshot("lomiri-calculator_caninfactdobasicmath")
+
+    machine.succeed("pkill -f lomiri-calculator-app")
+
+    with subtest("lomiri calculator localisation works"):
+        machine.execute("env LANG=de_DE.UTF-8 lomiri-calculator-app >&2 &")
+        machine.wait_for_text("Rechner")
+        machine.screenshot("lomiri-calculator_localised")
+
+    # History of previous run should have loaded
+    with subtest("lomiri calculator history works"):
+        machine.wait_for_text("352")
+  '';
+}
diff --git a/nixos/tests/lomiri-camera-app.nix b/nixos/tests/lomiri-camera-app.nix
new file mode 100644
index 0000000000000..ccd53a37135b2
--- /dev/null
+++ b/nixos/tests/lomiri-camera-app.nix
@@ -0,0 +1,135 @@
+{ lib, ... }:
+{
+  name = "lomiri-camera-app-standalone";
+  meta.maintainers = lib.teams.lomiri.members;
+
+  nodes.machine =
+    { config, pkgs, ... }:
+    {
+      imports = [ ./common/x11.nix ];
+
+      services.xserver.enable = true;
+
+      environment = {
+        systemPackages =
+          with pkgs;
+          [
+            feh # view photo result
+            ffmpeg # fake webcam stream
+            gnome-text-editor # somewhere to paste QR result
+            (imagemagick.override { ghostscriptSupport = true; }) # add label for OCR
+            qrtool # generate QR code
+            xdotool # clicking on QR button
+          ]
+          ++ (with pkgs.lomiri; [
+            suru-icon-theme
+            lomiri-camera-app
+          ]);
+        variables = {
+          UITK_ICON_THEME = "suru";
+        };
+      };
+
+      i18n.supportedLocales = [ "all" ];
+
+      fonts = {
+        packages = with pkgs; [
+          # Intended font & helps with OCR
+          ubuntu-classic
+        ];
+      };
+
+      # Fake camera
+      boot.extraModulePackages = with config.boot.kernelPackages; [ v4l2loopback ];
+    };
+
+  enableOCR = true;
+
+  testScript =
+    let
+      qrLabel = "Image";
+      qrContent = "Test";
+    in
+    ''
+      machine.wait_for_x()
+
+      with subtest("lomiri camera launches"):
+          machine.succeed("lomiri-camera-app >&2 &")
+          machine.wait_for_text("Cannot access")
+          machine.screenshot("lomiri-camera_open")
+
+      machine.succeed("pkill -f lomiri-camera-app")
+
+      # Setup fake v4l2 camera
+      machine.succeed("modprobe v4l2loopback video_nr=10 card_label=Video-Loopback exclusive_caps=1")
+      machine.succeed("qrtool encode '${qrContent}' -s 20 -m 10 > qr.png")
+      # Horizontal flip, add text, flip back. Camera displays image mirrored, so need reversed text for OCR
+      machine.succeed("magick qr.png -flop -pointsize 70 -fill black -annotate +100+100 '${qrLabel}' -flop output.png")
+      machine.succeed("ffmpeg -re -loop 1 -i output.png -vf format=yuv420p -f v4l2 /dev/video10 -loglevel fatal >&2 &")
+
+      with subtest("lomiri camera uses camera"):
+          machine.succeed("lomiri-camera-app >&2 &")
+          machine.wait_for_text("${qrLabel}")
+          machine.screenshot("lomiri-camera_feed")
+
+          machine.succeed("xdotool mousemove 320 610 click 1") # take photo
+          machine.wait_until_succeeds("find /root/Pictures/camera.ubports -name '*.jpg'")
+
+          # Check that the image is correct
+          machine.send_key("ctrl-alt-right")
+          machine.succeed("magick /root/Pictures/camera.ubports/IMG_00000001.jpg -flop photo_flip.png")
+          machine.succeed("feh photo_flip.png >&2 &")
+          machine.wait_for_text("${qrLabel}")
+          machine.screenshot("lomiri-camera_photo")
+
+      machine.succeed("pkill -f feh")
+      machine.send_key("ctrl-alt-left")
+      machine.succeed("pkill -f lomiri-camera-app")
+
+      with subtest("lomiri barcode scanner uses camera"):
+          machine.succeed("lomiri-camera-app --mode=barcode-reader >&2 &")
+          machine.wait_for_text("${qrLabel}")
+          machine.succeed("xdotool mousemove 320 610 click 1") # open up QR decode result
+
+          # OCR is struggling to recognise the text. Click the clipboard button and paste the result somewhere else
+          machine.sleep(5)
+          machine.screenshot("lomiri-barcode_decode")
+          machine.succeed("xdotool mousemove 350 530 click 1")
+          machine.sleep(5)
+
+          # Need to make a new window without closing camera app, otherwise clipboard content gets lost?
+          machine.send_key("ctrl-alt-right")
+          machine.succeed("gnome-text-editor >&2 &")
+          machine.wait_for_text("New")
+
+          # Font size up to help with OCR
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+          machine.send_key("ctrl-kp_add")
+
+          machine.send_key("ctrl-v")
+          machine.wait_for_text("${qrContent}")
+
+      machine.succeed("pkill -f gnome-text-editor")
+      machine.send_key("ctrl-alt-left")
+      machine.succeed("pkill -f lomiri-camera-app")
+
+      with subtest("lomiri camera localisation works"):
+          machine.succeed("env LANG=de_DE.UTF-8 lomiri-camera-app >&2 &")
+          machine.wait_for_text("Kamera")
+          machine.screenshot("lomiri-camera_localised")
+    '';
+}
diff --git a/nixos/tests/lomiri-clock-app.nix b/nixos/tests/lomiri-clock-app.nix
new file mode 100644
index 0000000000000..9db5cee49cf7b
--- /dev/null
+++ b/nixos/tests/lomiri-clock-app.nix
@@ -0,0 +1,48 @@
+{ pkgs, lib, ... }:
+{
+  name = "lomiri-clock-app-standalone";
+  meta.maintainers = lib.teams.lomiri.members;
+
+  nodes.machine =
+    { config, pkgs, ... }:
+    {
+      imports = [ ./common/x11.nix ];
+
+      services.xserver.enable = true;
+
+      environment = {
+        systemPackages = with pkgs.lomiri; [
+          suru-icon-theme
+          lomiri-clock-app
+        ];
+        variables = {
+          UITK_ICON_THEME = "suru";
+        };
+      };
+
+      i18n.supportedLocales = [ "all" ];
+
+      fonts.packages = with pkgs; [
+        # Intended font & helps with OCR
+        ubuntu-classic
+      ];
+    };
+
+  enableOCR = true;
+
+  testScript = ''
+    machine.wait_for_x()
+
+    with subtest("lomiri clock launches"):
+        machine.execute("lomiri-clock-app >&2 &")
+        machine.wait_for_text(r"(clock.ubports|City|Alarms)")
+        machine.screenshot("lomiri-clock_open")
+
+    machine.succeed("pkill -f lomiri-clock-app")
+
+    with subtest("lomiri clock localisation works"):
+        machine.execute("env LANG=de_DE.UTF-8 lomiri-clock-app >&2 &")
+        machine.wait_for_text(r"(Stadt|Weckzeiten)")
+        machine.screenshot("lomiri-clock_localised")
+  '';
+}
diff --git a/nixos/tests/lomiri-filemanager-app.nix b/nixos/tests/lomiri-filemanager-app.nix
index de42c9d150894..efde3a01c1145 100644
--- a/nixos/tests/lomiri-filemanager-app.nix
+++ b/nixos/tests/lomiri-filemanager-app.nix
@@ -24,7 +24,7 @@
 
       fonts.packages = with pkgs; [
         # Intended font & helps with OCR
-        ubuntu_font_family
+        ubuntu-classic
       ];
     };
 
diff --git a/nixos/tests/lomiri-system-settings.nix b/nixos/tests/lomiri-system-settings.nix
index 867fc14797e77..fac5184847520 100644
--- a/nixos/tests/lomiri-system-settings.nix
+++ b/nixos/tests/lomiri-system-settings.nix
@@ -23,7 +23,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
     fonts.packages = with pkgs; [
       # Intended font & helps with OCR
-      ubuntu_font_family
+      ubuntu-classic
     ];
 
     services.upower.enable = true;
diff --git a/nixos/tests/lomiri.nix b/nixos/tests/lomiri.nix
index d1fbab7aba082..94f1bd032c72b 100644
--- a/nixos/tests/lomiri.nix
+++ b/nixos/tests/lomiri.nix
@@ -1,339 +1,496 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }: let
+let
+  makeTest = import ./make-test-python.nix;
   # Just to make sure everything is the same, need it for OCR & navigating greeter
   user = "alice";
   description = "Alice Foobar";
   password = "foobar";
-in {
-  name = "lomiri";
-
-  meta = {
-    maintainers = lib.teams.lomiri.members;
-  };
-
-  nodes.machine = { config, ... }: {
-    imports = [
-      ./common/user-account.nix
-    ];
-
-    users.users.${user} = {
-      inherit description password;
-    };
-
-    # To control mouse via scripting
-    programs.ydotool.enable = true;
-
-    services.desktopManager.lomiri.enable = lib.mkForce true;
-    services.displayManager.defaultSession = lib.mkForce "lomiri";
-
-    # Help with OCR
-    fonts.packages = [ pkgs.inconsolata ];
-
-    environment = {
-      # Help with OCR
-      etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
-        font = rec {
-          normal.family = "Inconsolata";
-          bold.family = normal.family;
-          italic.family = normal.family;
-          bold_italic.family = normal.family;
-          size = 16;
+in
+{
+  greeter = makeTest (
+    { pkgs, lib, ... }:
+    {
+      name = "lomiri-greeter";
+
+      meta = {
+        maintainers = lib.teams.lomiri.members;
+      };
+
+      nodes.machine =
+        { config, ... }:
+        {
+          imports = [ ./common/user-account.nix ];
+
+          virtualisation.memorySize = 2047;
+
+          users.users.${user} = {
+            inherit description password;
+          };
+
+          services.desktopManager.lomiri.enable = lib.mkForce true;
+          services.displayManager.defaultSession = lib.mkForce "lomiri";
+
+          # Help with OCR
+          fonts.packages = [ pkgs.inconsolata ];
         };
-        colors = rec {
-          primary = {
-            foreground = "0x000000";
-            background = "0xffffff";
+
+      enableOCR = true;
+
+      testScript =
+        { nodes, ... }:
+        ''
+          start_all()
+          machine.wait_for_unit("multi-user.target")
+
+          # Lomiri in greeter mode should work & be able to start a session
+          with subtest("lomiri greeter works"):
+              machine.wait_for_unit("display-manager.service")
+              machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
+
+              # Start page shows current time
+              machine.wait_for_text(r"(AM|PM)")
+              machine.screenshot("lomiri_greeter_launched")
+
+              # Advance to login part
+              machine.send_key("ret")
+              machine.wait_for_text("${description}")
+              machine.screenshot("lomiri_greeter_login")
+
+              # Login
+              machine.send_chars("${password}\n")
+              machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
+
+              # Output rendering from Lomiri has started when it starts printing performance diagnostics
+              machine.wait_for_console_text("Last frame took")
+              # Look for datetime's clock, one of the last elements to load
+              machine.wait_for_text(r"(AM|PM)")
+              machine.screenshot("lomiri_launched")
+        '';
+    }
+  );
+
+  desktop = makeTest (
+    { pkgs, lib, ... }:
+    {
+      name = "lomiri-desktop";
+
+      meta = {
+        maintainers = lib.teams.lomiri.members;
+      };
+
+      nodes.machine =
+        { config, ... }:
+        {
+          imports = [
+            ./common/auto.nix
+            ./common/user-account.nix
+          ];
+
+          virtualisation.memorySize = 2047;
+
+          users.users.${user} = {
+            inherit description password;
+            # polkit agent test
+            extraGroups = [ "wheel" ];
           };
-          normal = {
-            green = primary.foreground;
+
+          test-support.displayManager.auto = {
+            enable = true;
+            inherit user;
+          };
+
+          # To control mouse via scripting
+          programs.ydotool.enable = true;
+
+          services.desktopManager.lomiri.enable = lib.mkForce true;
+          services.displayManager.defaultSession = lib.mkForce "lomiri";
+
+          # Help with OCR
+          fonts.packages = [ pkgs.inconsolata ];
+
+          environment = {
+            # Help with OCR
+            etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
+              font = rec {
+                normal.family = "Inconsolata";
+                bold.family = normal.family;
+                italic.family = normal.family;
+                bold_italic.family = normal.family;
+                size = 16;
+              };
+              colors = rec {
+                primary = {
+                  foreground = "0x000000";
+                  background = "0xffffff";
+                };
+                normal = {
+                  green = primary.foreground;
+                };
+              };
+            };
+
+            variables = {
+              # So we can test what content-hub is working behind the scenes
+              CONTENT_HUB_LOGGING_LEVEL = "2";
+            };
+
+            systemPackages = with pkgs; [
+              # For a convenient way of kicking off content-hub peer collection
+              lomiri.content-hub.examples
+
+              # Forcing alacritty to run as an X11 app when opened from the starter menu
+              (symlinkJoin {
+                name = "x11-${alacritty.name}";
+
+                paths = [ alacritty ];
+
+                nativeBuildInputs = [ makeWrapper ];
+
+                postBuild = ''
+                  wrapProgram $out/bin/alacritty \
+                    --set WINIT_UNIX_BACKEND x11 \
+                    --set WAYLAND_DISPLAY ""
+                '';
+
+                inherit (alacritty) meta;
+              })
+            ];
           };
+
+          # Help with OCR
+          systemd.tmpfiles.settings =
+            let
+              white = "255, 255, 255";
+              black = "0, 0, 0";
+              colorSection = color: {
+                Color = color;
+                Bold = true;
+                Transparency = false;
+              };
+              terminalColors = pkgs.writeText "customized.colorscheme" (
+                lib.generators.toINI { } {
+                  Background = colorSection white;
+                  Foreground = colorSection black;
+                  Color2 = colorSection black;
+                  Color2Intense = colorSection black;
+                }
+              );
+              terminalConfig = pkgs.writeText "terminal.ubports.conf" (
+                lib.generators.toINI { } {
+                  General = {
+                    colorScheme = "customized";
+                    fontSize = "16";
+                    fontStyle = "Inconsolata";
+                  };
+                }
+              );
+              confBase = "${config.users.users.${user}.home}/.config";
+              userDirArgs = {
+                mode = "0700";
+                user = user;
+                group = "users";
+              };
+            in
+            {
+              "10-lomiri-test-setup" = {
+                "${confBase}".d = userDirArgs;
+                "${confBase}/terminal.ubports".d = userDirArgs;
+                "${confBase}/terminal.ubports/customized.colorscheme".L.argument = "${terminalColors}";
+                "${confBase}/terminal.ubports/terminal.ubports.conf".L.argument = "${terminalConfig}";
+              };
+            };
         };
-      };
 
-      variables = {
-        # So we can test what content-hub is working behind the scenes
-        CONTENT_HUB_LOGGING_LEVEL = "2";
+      enableOCR = true;
+
+      testScript =
+        { nodes, ... }:
+        ''
+          def toggle_maximise():
+              """
+              Maximise the current window.
+              """
+              machine.send_key("ctrl-meta_l-up")
+
+              # For some reason, Lomiri in these VM tests very frequently opens the starter menu a few seconds after sending the above.
+              # Because this isn't 100% reproducible all the time, and there is no command to await when OCR doesn't pick up some text,
+              # the best we can do is send some Escape input after waiting some arbitrary time and hope that it works out fine.
+              machine.sleep(5)
+              machine.send_key("esc")
+              machine.sleep(5)
+
+          def mouse_click(xpos, ypos):
+              """
+              Move the mouse to a screen location and hit left-click.
+              """
+
+              # Need to reset to top-left, --absolute doesn't work?
+              machine.execute("ydotool mousemove -- -10000 -10000")
+              machine.sleep(2)
+
+              # Move
+              machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
+              machine.sleep(2)
+
+              # Click (C0 - left button: down & up)
+              machine.execute("ydotool click 0xC0")
+              machine.sleep(2)
+
+          def open_starter():
+              """
+              Open the starter, and ensure it's opened.
+              """
+
+              # Using the keybind has a chance of instantly closing the menu again? Just click the button
+              mouse_click(20, 30)
+
+          start_all()
+          machine.wait_for_unit("multi-user.target")
+
+          # The session should start, and not be stuck in i.e. a crash loop
+          with subtest("lomiri starts"):
+              machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
+              # Output rendering from Lomiri has started when it starts printing performance diagnostics
+              machine.wait_for_console_text("Last frame took")
+              # Look for datetime's clock, one of the last elements to load
+              machine.wait_for_text(r"(AM|PM)")
+              machine.screenshot("lomiri_launched")
+
+          # Working terminal keybind is good
+          with subtest("terminal keybind works"):
+              machine.send_key("ctrl-alt-t")
+              machine.wait_for_text(r"(${user}|machine)")
+              machine.screenshot("terminal_opens")
+
+              # lomiri-terminal-app has a separate VM test to test its basic functionality
+
+              # for the LSS content-hub test to work reliably, we need to kick off peer collecting
+              machine.send_chars("content-hub-test-importer\n")
+              machine.wait_for_text(r"(/build/source|hub.cpp|handler.cpp|void|virtual|const)") # awaiting log messages from content-hub
+              machine.send_key("ctrl-c")
+
+              # Doing this here, since we need an in-session shell & separately starting a terminal again wastes time
+              with subtest("polkit agent works"):
+                  machine.send_chars("pkexec touch /tmp/polkit-test\n")
+                  # There's an authentication notification here that gains focus, but we struggle with OCRing it
+                  # Just hope that it's up after a short wait
+                  machine.sleep(10)
+                  machine.screenshot("polkit_agent")
+                  machine.send_chars("${password}")
+                  machine.sleep(2) # Hopefully enough delay to make sure all the password characters have been registered? Maybe just placebo
+                  machine.send_chars("\n")
+                  machine.wait_for_file("/tmp/polkit-test", 10)
+
+              machine.send_key("alt-f4")
+
+          # We want the ability to launch applications
+          with subtest("starter menu works"):
+              open_starter()
+              machine.screenshot("starter_opens")
+
+              # Just try the terminal again, we know that it should work
+              machine.send_chars("Terminal\n")
+              machine.wait_for_text(r"(${user}|machine)")
+              machine.send_key("alt-f4")
+
+          # We want support for X11 apps
+          with subtest("xwayland support works"):
+              open_starter()
+              machine.send_chars("Alacritty\n")
+              machine.wait_for_text(r"(${user}|machine)")
+              machine.screenshot("alacritty_opens")
+              machine.send_key("alt-f4")
+
+          # Morph is how we go online
+          with subtest("morph browser works"):
+              open_starter()
+              machine.send_chars("Morph\n")
+              machine.wait_for_text(r"(Bookmarks|address|site|visited any)")
+              machine.screenshot("morph_open")
+
+              # morph-browser has a separate VM test, there isn't anything new we could test here
+
+              machine.send_key("alt-f4")
+
+          # LSS provides DE settings
+          with subtest("system settings open"):
+              open_starter()
+              machine.send_chars("System Settings\n")
+              machine.wait_for_text("Rotation Lock")
+              machine.screenshot("settings_open")
+
+              # lomiri-system-settings has a separate VM test, only test Lomiri-specific content-hub functionalities here
+
+              # Make fullscreen, can't navigate to Background plugin via keyboard unless window has non-phone-like aspect ratio
+              toggle_maximise()
+
+              # Load Background plugin
+              machine.send_key("tab")
+              machine.send_key("tab")
+              machine.send_key("tab")
+              machine.send_key("tab")
+              machine.send_key("tab")
+              machine.send_key("tab")
+              machine.send_key("ret")
+              machine.wait_for_text("Background image")
+
+              # Try to load custom background
+              machine.send_key("shift-tab")
+              machine.send_key("shift-tab")
+              machine.send_key("shift-tab")
+              machine.send_key("shift-tab")
+              machine.send_key("shift-tab")
+              machine.send_key("shift-tab")
+              machine.send_key("ret")
+
+              # Peers should be loaded
+              machine.wait_for_text("Morph") # or Gallery, but Morph is already packaged
+              machine.screenshot("settings_content-hub_peers")
+
+              # Select Morph as content source
+              mouse_click(370, 100)
+
+              # Expect Morph to be brought into the foreground, with its Downloads page open
+              machine.wait_for_text("No downloads")
+
+              # If content-hub encounters a problem, it may have crashed the original application issuing the request.
+              # Check that it's still alive
+              machine.succeed("pgrep -u ${user} -f lomiri-system-settings")
+
+              machine.screenshot("content-hub_exchange")
+
+              # Testing any more would require more applications & setup, the fact that it's already being attempted is a good sign
+              machine.send_key("esc")
+
+              machine.sleep(2) # sleep a tiny bit so morph can close & the focus can return to LSS
+              machine.send_key("alt-f4")
+        '';
+    }
+  );
+
+  desktop-ayatana-indicators = makeTest (
+    { pkgs, lib, ... }:
+    {
+      name = "lomiri-desktop-ayatana-indicators";
+
+      meta = {
+        maintainers = lib.teams.lomiri.members;
       };
 
-      systemPackages = with pkgs; [
-        # For a convenient way of kicking off content-hub peer collection
-        lomiri.content-hub.examples
-
-        # Forcing alacritty to run as an X11 app when opened from the starter menu
-        (symlinkJoin {
-          name = "x11-${alacritty.name}";
-
-          paths = [ alacritty ];
-
-          nativeBuildInputs = [ makeWrapper ];
-
-          postBuild = ''
-            wrapProgram $out/bin/alacritty \
-              --set WINIT_UNIX_BACKEND x11 \
-              --set WAYLAND_DISPLAY ""
-          '';
-
-          inherit (alacritty) meta;
-        })
-      ];
-    };
-
-    # Help with OCR
-    systemd.tmpfiles.settings = let
-      white = "255, 255, 255";
-      black = "0, 0, 0";
-      colorSection = color: {
-        Color = color;
-        Bold = true;
-        Transparency = false;
-      };
-      terminalColors = pkgs.writeText "customized.colorscheme" (lib.generators.toINI {} {
-        Background = colorSection white;
-        Foreground = colorSection black;
-        Color2 = colorSection black;
-        Color2Intense = colorSection black;
-      });
-      terminalConfig = pkgs.writeText "terminal.ubports.conf" (lib.generators.toINI {} {
-        General = {
-          colorScheme = "customized";
-          fontSize = "16";
-          fontStyle = "Inconsolata";
+      nodes.machine =
+        { config, ... }:
+        {
+          imports = [
+            ./common/auto.nix
+            ./common/user-account.nix
+          ];
+
+          virtualisation.memorySize = 2047;
+
+          users.users.${user} = {
+            inherit description password;
+          };
+
+          test-support.displayManager.auto = {
+            enable = true;
+            inherit user;
+          };
+
+          # To control mouse via scripting
+          programs.ydotool.enable = true;
+
+          services.desktopManager.lomiri.enable = lib.mkForce true;
+          services.displayManager.defaultSession = lib.mkForce "lomiri";
+
+          # Help with OCR
+          fonts.packages = [ pkgs.inconsolata ];
         };
-      });
-      confBase = "${config.users.users.${user}.home}/.config";
-      userDirArgs = {
-        mode = "0700";
-        user = user;
-        group = "users";
-      };
-    in {
-      "10-lomiri-test-setup" = {
-        "${confBase}".d = userDirArgs;
-        "${confBase}/terminal.ubports".d = userDirArgs;
-        "${confBase}/terminal.ubports/customized.colorscheme".L.argument = "${terminalColors}";
-        "${confBase}/terminal.ubports/terminal.ubports.conf".L.argument = "${terminalConfig}";
-      };
-    };
-  };
-
-  enableOCR = true;
-
-  testScript = { nodes, ... }: ''
-    def toggle_maximise():
-        """
-        Maximise the current window.
-        """
-        machine.send_key("ctrl-meta_l-up")
-
-        # For some reason, Lomiri in these VM tests very frequently opens the starter menu a few seconds after sending the above.
-        # Because this isn't 100% reproducible all the time, and there is no command to await when OCR doesn't pick up some text,
-        # the best we can do is send some Escape input after waiting some arbitrary time and hope that it works out fine.
-        machine.sleep(5)
-        machine.send_key("esc")
-        machine.sleep(5)
-
-    def mouse_click(xpos, ypos):
-        """
-        Move the mouse to a screen location and hit left-click.
-        """
-
-        # Need to reset to top-left, --absolute doesn't work?
-        machine.execute("ydotool mousemove -- -10000 -10000")
-        machine.sleep(2)
-
-        # Move
-        machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
-        machine.sleep(2)
-
-        # Click (C0 - left button: down & up)
-        machine.execute("ydotool click 0xC0")
-        machine.sleep(2)
-
-    def open_starter():
-        """
-        Open the starter, and ensure it's opened.
-        """
-
-        # Using the keybind has a chance of instantly closing the menu again? Just click the button
-        mouse_click(20, 30)
-
-        # Look for Search box & GUI-less content-hub examples, highest chances of avoiding false positives
-        machine.wait_for_text(r"(Search|Export|Import|Share)")
-
-    start_all()
-    machine.wait_for_unit("multi-user.target")
-
-    # Lomiri in greeter mode should work & be able to start a session
-    with subtest("lomiri greeter works"):
-        machine.wait_for_unit("display-manager.service")
-        machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
-
-        # Start page shows current time
-        machine.wait_for_text(r"(AM|PM)")
-        machine.screenshot("lomiri_greeter_launched")
-
-        # Advance to login part
-        machine.send_key("ret")
-        machine.wait_for_text("${description}")
-        machine.screenshot("lomiri_greeter_login")
-
-        # Login
-        machine.send_chars("${password}\n")
-        machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
-
-    # The session should start, and not be stuck in i.e. a crash loop
-    with subtest("lomiri starts"):
-        # Output rendering from Lomiri has started when it starts printing performance diagnostics
-        machine.wait_for_console_text("Last frame took")
-        # Look for datetime's clock, one of the last elements to load
-        machine.wait_for_text(r"(AM|PM)")
-        machine.screenshot("lomiri_launched")
-
-    # Working terminal keybind is good
-    with subtest("terminal keybind works"):
-        machine.send_key("ctrl-alt-t")
-        machine.wait_for_text(r"(${user}|machine)")
-        machine.screenshot("terminal_opens")
-
-        # lomiri-terminal-app has a separate VM test to test its basic functionality
-
-        # for the LSS content-hub test to work reliably, we need to kick off peer collecting
-        machine.send_chars("content-hub-test-importer\n")
-        machine.wait_for_text(r"(/build/source|hub.cpp|handler.cpp|void|virtual|const)") # awaiting log messages from content-hub
-        machine.send_key("ctrl-c")
-
-        machine.send_key("alt-f4")
-
-    # We want the ability to launch applications
-    with subtest("starter menu works"):
-        open_starter()
-        machine.screenshot("starter_opens")
-
-        # Just try the terminal again, we know that it should work
-        machine.send_chars("Terminal\n")
-        machine.wait_for_text(r"(${user}|machine)")
-        machine.send_key("alt-f4")
-
-    # We want support for X11 apps
-    with subtest("xwayland support works"):
-        open_starter()
-        machine.send_chars("Alacritty\n")
-        machine.wait_for_text(r"(${user}|machine)")
-        machine.screenshot("alacritty_opens")
-        machine.send_key("alt-f4")
-
-    # Morph is how we go online
-    with subtest("morph browser works"):
-        open_starter()
-        machine.send_chars("Morph\n")
-        machine.wait_for_text(r"(Bookmarks|address|site|visited any)")
-        machine.screenshot("morph_open")
-
-        # morph-browser has a separate VM test, there isn't anything new we could test here
-
-        machine.send_key("alt-f4")
-
-    # LSS provides DE settings
-    with subtest("system settings open"):
-        open_starter()
-        machine.send_chars("System Settings\n")
-        machine.wait_for_text("Rotation Lock")
-        machine.screenshot("settings_open")
-
-        # lomiri-system-settings has a separate VM test, only test Lomiri-specific content-hub functionalities here
-
-        # Make fullscreen, can't navigate to Background plugin via keyboard unless window has non-phone-like aspect ratio
-        toggle_maximise()
-
-        # Load Background plugin
-        machine.send_key("tab")
-        machine.send_key("tab")
-        machine.send_key("tab")
-        machine.send_key("tab")
-        machine.send_key("tab")
-        machine.send_key("tab")
-        machine.send_key("ret")
-        machine.wait_for_text("Background image")
-
-        # Try to load custom background
-        machine.send_key("shift-tab")
-        machine.send_key("shift-tab")
-        machine.send_key("shift-tab")
-        machine.send_key("shift-tab")
-        machine.send_key("shift-tab")
-        machine.send_key("shift-tab")
-        machine.send_key("ret")
-
-        # Peers should be loaded
-        machine.wait_for_text("Morph") # or Gallery, but Morph is already packaged
-        machine.screenshot("settings_content-hub_peers")
-
-        # Select Morph as content source
-        mouse_click(370, 100)
-
-        # Expect Morph to be brought into the foreground, with its Downloads page open
-        machine.wait_for_text("No downloads")
-
-        # If content-hub encounters a problem, it may have crashed the original application issuing the request.
-        # Check that it's still alive
-        machine.succeed("pgrep -u ${user} -f lomiri-system-settings")
-
-        machine.screenshot("content-hub_exchange")
-
-        # Testing any more would require more applications & setup, the fact that it's already being attempted is a good sign
-        machine.send_key("esc")
-
-        machine.send_key("alt-f4")
-
-    # The ayatana indicators are an important part of the experience, and they hold the only graphical way of exiting the session.
-    # There's a test app we could use that also displays their contents, but it's abit inconsistent.
-    with subtest("ayatana indicators work"):
-        mouse_click(735, 0) # the cog in the top-right, for the session indicator
-        machine.wait_for_text(r"(Notifications|Rotation|Battery|Sound|Time|Date|System)")
-        machine.screenshot("indicators_open")
-
-        # Indicator order within the menus *should* be fixed based on per-indicator order setting
-        # Session is the one we clicked, but the last we should test (logout). Go as far left as we can test.
-        machine.send_key("left")
-        machine.send_key("left")
-        machine.send_key("left")
-        machine.send_key("left")
-        machine.send_key("left")
-        # Notifications are usually empty, nothing to check there
-
-        with subtest("ayatana indicator display works"):
-            # We start on this, don't go right
-            machine.wait_for_text("Lock")
-            machine.screenshot("indicators_display")
-
-        with subtest("lomiri indicator network works"):
-            machine.send_key("right")
-            machine.wait_for_text(r"(Flight|Wi-Fi)")
-            machine.screenshot("indicators_network")
-
-        with subtest("ayatana indicator sound works"):
-            machine.send_key("right")
-            machine.wait_for_text(r"(Silent|Volume)")
-            machine.screenshot("indicators_sound")
-
-        with subtest("ayatana indicator power works"):
-            machine.send_key("right")
-            machine.wait_for_text(r"(Charge|Battery settings)")
-            machine.screenshot("indicators_power")
-
-        with subtest("ayatana indicator datetime works"):
-            machine.send_key("right")
-            machine.wait_for_text("Time and Date Settings")
-            machine.screenshot("indicators_timedate")
-
-        with subtest("ayatana indicator session works"):
-            machine.send_key("right")
-            machine.wait_for_text("Log Out")
-            machine.screenshot("indicators_session")
-
-            # We should be able to log out and return to the greeter
-            mouse_click(720, 280) # "Log Out"
-            mouse_click(400, 240) # confirm logout
-            machine.wait_until_fails("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
-            machine.wait_until_succeeds("pgrep -u lightdm -f 'lomiri --mode=greeter'")
-  '';
-})
+
+      enableOCR = true;
+
+      testScript =
+        { nodes, ... }:
+        ''
+          def mouse_click(xpos, ypos):
+              """
+              Move the mouse to a screen location and hit left-click.
+              """
+
+              # Need to reset to top-left, --absolute doesn't work?
+              machine.execute("ydotool mousemove -- -10000 -10000")
+              machine.sleep(2)
+
+              # Move
+              machine.execute(f"ydotool mousemove -- {xpos} {ypos}")
+              machine.sleep(2)
+
+              # Click (C0 - left button: down & up)
+              machine.execute("ydotool click 0xC0")
+              machine.sleep(2)
+
+          start_all()
+          machine.wait_for_unit("multi-user.target")
+
+          # The session should start, and not be stuck in i.e. a crash loop
+          with subtest("lomiri starts"):
+              machine.wait_until_succeeds("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
+              # Output rendering from Lomiri has started when it starts printing performance diagnostics
+              machine.wait_for_console_text("Last frame took")
+              # Look for datetime's clock, one of the last elements to load
+              machine.wait_for_text(r"(AM|PM)")
+              machine.screenshot("lomiri_launched")
+
+          # The ayatana indicators are an important part of the experience, and they hold the only graphical way of exiting the session.
+          # There's a test app we could use that also displays their contents, but it's abit inconsistent.
+          with subtest("ayatana indicators work"):
+              mouse_click(735, 0) # the cog in the top-right, for the session indicator
+              machine.wait_for_text(r"(Notifications|Rotation|Battery|Sound|Time|Date|System)")
+              machine.screenshot("indicators_open")
+
+              # Indicator order within the menus *should* be fixed based on per-indicator order setting
+              # Session is the one we clicked, but the last we should test (logout). Go as far left as we can test.
+              machine.send_key("left")
+              machine.send_key("left")
+              machine.send_key("left")
+              machine.send_key("left")
+              machine.send_key("left")
+              # Notifications are usually empty, nothing to check there
+
+              with subtest("ayatana indicator display works"):
+                  # We start on this, don't go right
+                  machine.wait_for_text("Lock")
+                  machine.screenshot("indicators_display")
+
+              with subtest("lomiri indicator network works"):
+                  machine.send_key("right")
+                  machine.wait_for_text(r"(Flight|Wi-Fi)")
+                  machine.screenshot("indicators_network")
+
+              with subtest("ayatana indicator sound works"):
+                  machine.send_key("right")
+                  machine.wait_for_text(r"(Silent|Volume)")
+                  machine.screenshot("indicators_sound")
+
+              with subtest("ayatana indicator power works"):
+                  machine.send_key("right")
+                  machine.wait_for_text(r"(Charge|Battery settings)")
+                  machine.screenshot("indicators_power")
+
+              with subtest("ayatana indicator datetime works"):
+                  machine.send_key("right")
+                  machine.wait_for_text("Time and Date Settings")
+                  machine.screenshot("indicators_timedate")
+
+              with subtest("ayatana indicator session works"):
+                  machine.send_key("right")
+                  machine.wait_for_text("Log Out")
+                  machine.screenshot("indicators_session")
+
+                  # We should be able to log out and return to the greeter
+                  mouse_click(720, 280) # "Log Out"
+                  mouse_click(400, 240) # confirm logout
+                  machine.wait_until_fails("pgrep -u ${user} -f 'lomiri --mode=full-shell'")
+        '';
+    }
+  );
+
+}
diff --git a/nixos/tests/lorri/default.nix b/nixos/tests/lorri/default.nix
index a4bdc92490ce1..e9e26c03f6ca1 100644
--- a/nixos/tests/lorri/default.nix
+++ b/nixos/tests/lorri/default.nix
@@ -17,12 +17,12 @@ import ../make-test-python.nix {
 
     # Start the daemon and wait until it is ready
     machine.execute("lorri daemon > lorri.stdout 2> lorri.stderr &")
-    machine.wait_until_succeeds("grep --fixed-strings 'ready' lorri.stdout")
+    machine.wait_until_succeeds("grep --fixed-strings 'ready' lorri.stderr")
 
     # Ping the daemon
-    machine.succeed("lorri internal ping shell.nix")
+    machine.succeed("lorri internal ping --shell-file shell.nix")
 
     # Wait for the daemon to finish the build
-    machine.wait_until_succeeds("grep --fixed-strings 'Completed' lorri.stdout")
+    machine.wait_until_succeeds("grep --fixed-strings 'Completed' lorri.stderr")
   '';
 }
diff --git a/nixos/tests/lxd/container.nix b/nixos/tests/lxd/container.nix
index ef9c3f4bbee7e..26dcad2f97a77 100644
--- a/nixos/tests/lxd/container.nix
+++ b/nixos/tests/lxd/container.nix
@@ -18,10 +18,6 @@ let
 in {
   name = "lxd-container";
 
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
   nodes.machine = { lib, ... }: {
     virtualisation = {
       diskSize = 6144;
diff --git a/nixos/tests/lxd/nftables.nix b/nixos/tests/lxd/nftables.nix
index e6ce4089d719d..d419f9b66af13 100644
--- a/nixos/tests/lxd/nftables.nix
+++ b/nixos/tests/lxd/nftables.nix
@@ -8,10 +8,6 @@
 import ../make-test-python.nix ({ pkgs, lib, ...} : {
   name = "lxd-nftables";
 
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
   nodes.machine = { lib, ... }: {
     virtualisation = {
       lxd.enable = true;
diff --git a/nixos/tests/lxd/preseed.nix b/nixos/tests/lxd/preseed.nix
index fb80dcf3893e4..2e0ff33d521fe 100644
--- a/nixos/tests/lxd/preseed.nix
+++ b/nixos/tests/lxd/preseed.nix
@@ -3,10 +3,6 @@ import ../make-test-python.nix ({ pkgs, lib, ... } :
 {
   name = "lxd-preseed";
 
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
   nodes.machine = { lib, ... }: {
     virtualisation = {
       diskSize = 4096;
diff --git a/nixos/tests/lxd/ui.nix b/nixos/tests/lxd/ui.nix
index c442f44ab81cd..f96c3d74d93cd 100644
--- a/nixos/tests/lxd/ui.nix
+++ b/nixos/tests/lxd/ui.nix
@@ -1,10 +1,6 @@
-import ../make-test-python.nix ({ pkgs, lib, ... }: {
+import ../make-test-python.nix ({ pkgs, ... }: {
   name = "lxd-ui";
 
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
   nodes.machine = { lib, ... }: {
     virtualisation = {
       lxd.enable = true;
diff --git a/nixos/tests/lxd/virtual-machine.nix b/nixos/tests/lxd/virtual-machine.nix
index 2a9dd8fcdbf61..14c5e8a82aa8f 100644
--- a/nixos/tests/lxd/virtual-machine.nix
+++ b/nixos/tests/lxd/virtual-machine.nix
@@ -18,10 +18,6 @@ let
 in {
   name = "lxd-virtual-machine";
 
-  meta = {
-    maintainers = lib.teams.lxc.members;
-  };
-
   nodes.machine = {lib, ...}: {
     virtualisation = {
       diskSize = 4096;
diff --git a/nixos/tests/ly.nix b/nixos/tests/ly.nix
new file mode 100644
index 0000000000000..04c6ed9c7774b
--- /dev/null
+++ b/nixos/tests/ly.nix
@@ -0,0 +1,44 @@
+import ./make-test-python.nix (
+  { ... }:
+
+  {
+    name = "ly";
+
+    nodes.machine =
+      { ... }:
+      {
+        imports = [ ./common/user-account.nix ];
+        services.displayManager.ly = {
+          enable = true;
+          settings = {
+            load = false;
+            save = false;
+          };
+        };
+        services.xserver.enable = true;
+        services.displayManager.defaultSession = "none+icewm";
+        services.xserver.windowManager.icewm.enable = true;
+      };
+
+    testScript =
+      { nodes, ... }:
+      let
+        user = nodes.machine.users.users.alice;
+      in
+      ''
+        start_all()
+        machine.wait_until_tty_matches("2", "password:")
+        machine.send_key("ctrl-alt-f2")
+        machine.sleep(1)
+        machine.screenshot("ly")
+        machine.send_chars("alice")
+        machine.send_key("tab")
+        machine.send_chars("${user.password}")
+        machine.send_key("ret")
+        machine.wait_for_file("/run/user/${toString user.uid}/lyxauth")
+        machine.succeed("xauth merge /run/user/${toString user.uid}/lyxauth")
+        machine.wait_for_window("^IceWM ")
+        machine.screenshot("icewm")
+      '';
+  }
+)
diff --git a/nixos/tests/mediatomb.nix b/nixos/tests/mediatomb.nix
index 9c84aa3e92a5d..5718a9a4a2992 100644
--- a/nixos/tests/mediatomb.nix
+++ b/nixos/tests/mediatomb.nix
@@ -30,15 +30,22 @@ import ./make-test-python.nix {
     client = {};
   };
 
-  testScript = ''
-    start_all()
+  testScript = { nodes, ... }:
+    let
+      serverIP = nodes.server.networking.primaryIPAddress;
+      serverIPv6 = nodes.server.networking.primaryIPv6Address;
+    in
+    ''
+      start_all()
 
-    server.wait_for_unit("mediatomb")
-    server.wait_until_succeeds("nc -z 192.168.1.2 49152")
-    server.succeed("curl -v --fail http://server:49152/")
+      server.wait_for_unit("mediatomb")
+      server.wait_until_succeeds("nc -z ${serverIP} 49152")
+      server.succeed("curl -v --fail http://${serverIP}:49152/")
+      server.succeed("curl -v --fail http://[${serverIPv6}]:49152/")
 
-    client.wait_for_unit("multi-user.target")
-    page = client.succeed("curl -v --fail http://server:49152/")
-    assert "Gerbera" in page and "MediaTomb" not in page
-  '';
+      client.wait_for_unit("multi-user.target")
+      page = client.succeed("curl -v --fail http://${serverIP}:49152/")
+      page = client.succeed("curl -v --fail http://[${serverIPv6}]:49152/")
+      assert "Gerbera" in page and "MediaTomb" not in page
+    '';
 }
diff --git a/nixos/tests/miracle-wm.nix b/nixos/tests/miracle-wm.nix
new file mode 100644
index 0000000000000..2bb62222b22a1
--- /dev/null
+++ b/nixos/tests/miracle-wm.nix
@@ -0,0 +1,131 @@
+{ pkgs, lib, ... }:
+{
+  name = "miracle-wm";
+
+  meta = {
+    maintainers = with lib.maintainers; [ OPNA2608 ];
+  };
+
+  nodes.machine =
+    { config, ... }:
+    {
+      imports = [
+        ./common/auto.nix
+        ./common/user-account.nix
+      ];
+
+      # Seems to very rarely get interrupted by oom-killer
+      virtualisation.memorySize = 2047;
+
+      test-support.displayManager.auto = {
+        enable = true;
+        user = "alice";
+      };
+
+      services.xserver.enable = true;
+      services.displayManager.defaultSession = lib.mkForce "miracle-wm";
+
+      programs.wayland.miracle-wm.enable = true;
+
+      # To ensure a specific config for the tests
+      systemd.tmpfiles.rules =
+        let
+          testConfig = (pkgs.formats.yaml { }).generate "miracle-wm.yaml" {
+            terminal = "env WINIT_UNIX_BACKEND=x11 WAYLAND_DISPLAY= alacritty";
+            startup_apps = [
+              {
+                command = "foot";
+                restart_on_death = false;
+              }
+            ];
+          };
+        in
+        [
+          "d ${config.users.users.alice.home}/.config 0700 alice users - -"
+          "L ${config.users.users.alice.home}/.config/miracle-wm.yaml - - - - ${testConfig}"
+        ];
+
+      environment = {
+        shellAliases = {
+          test-wayland = "wayland-info | tee /tmp/test-wayland.out && touch /tmp/test-wayland-exit-ok";
+          test-x11 = "glinfo | tee /tmp/test-x11.out && touch /tmp/test-x11-exit-ok";
+        };
+
+        systemPackages = with pkgs; [
+          mesa-demos
+          wayland-utils
+          foot
+          alacritty
+        ];
+
+        # To help with OCR
+        etc."xdg/foot/foot.ini".text = lib.generators.toINI { } {
+          main = {
+            font = "inconsolata:size=16";
+          };
+          colors = rec {
+            foreground = "000000";
+            background = "ffffff";
+            regular2 = foreground;
+          };
+        };
+        etc."xdg/alacritty/alacritty.yml".text = lib.generators.toYAML { } {
+          font = rec {
+            normal.family = "Inconsolata";
+            bold.family = normal.family;
+            italic.family = normal.family;
+            bold_italic.family = normal.family;
+            size = 16;
+          };
+          colors = rec {
+            primary = {
+              foreground = "0x000000";
+              background = "0xffffff";
+            };
+            normal = {
+              green = primary.foreground;
+            };
+          };
+        };
+      };
+
+      fonts.packages = [ pkgs.inconsolata ];
+    };
+
+  enableOCR = true;
+
+  testScript =
+    { ... }:
+    ''
+      start_all()
+      machine.wait_for_unit("multi-user.target")
+
+      # Wait for Miriway to complete startup
+      machine.wait_for_file("/run/user/1000/wayland-0")
+      machine.succeed("pgrep miracle-wm")
+      machine.screenshot("miracle-wm_launched")
+
+      # Test Wayland
+      with subtest("wayland client works"):
+          # We let miracle-wm start the first terminal, as we might get stuck if it's not ready to process the first keybind
+          # machine.send_key("ctrl-alt-t")
+          machine.wait_for_text("alice@machine")
+          machine.send_chars("test-wayland\n")
+          machine.wait_for_file("/tmp/test-wayland-exit-ok")
+          machine.copy_from_vm("/tmp/test-wayland.out")
+          machine.screenshot("foot_wayland_info")
+          machine.send_chars("exit\n")
+          machine.wait_until_fails("pgrep foot")
+
+      # Test XWayland
+      with subtest("x11 client works"):
+          machine.send_key("meta_l-ret")
+          machine.wait_for_text("alice@machine")
+          machine.send_chars("test-x11\n")
+          machine.wait_for_file("/tmp/test-x11-exit-ok")
+          machine.copy_from_vm("/tmp/test-x11.out")
+          machine.screenshot("alacritty_glinfo")
+          machine.send_chars("exit\n")
+          machine.wait_until_fails("pgrep alacritty")
+    '';
+}
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index 33b3ca2c11c2e..1d296accf121f 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -99,6 +99,9 @@ in {
       with subtest("whether systemd-tmpfiles settings works"):
           machine.succeed("[ -e /tmp/somefile ]")
 
+      with subtest("/etc/mtab"):
+          assert "/proc/mounts" == machine.succeed("readlink --no-newline /etc/mtab")
+
       with subtest("whether automounting works"):
           machine.fail("grep '/tmp2 tmpfs' /proc/mounts")
           machine.succeed("touch /tmp2/x")
diff --git a/nixos/tests/misskey.nix b/nixos/tests/misskey.nix
new file mode 100644
index 0000000000000..1a450c518aaeb
--- /dev/null
+++ b/nixos/tests/misskey.nix
@@ -0,0 +1,29 @@
+import ./make-test-python.nix (
+  { lib, ... }:
+  let
+    port = 61812;
+  in
+  {
+    name = "misskey";
+
+    meta.maintainers = [ lib.maintainers.feathecutie ];
+
+    nodes.machine = {
+      services.misskey = {
+        enable = true;
+        settings = {
+          url = "http://misskey.local";
+          inherit port;
+        };
+        database.createLocally = true;
+        redis.createLocally = true;
+      };
+    };
+
+    testScript = ''
+      machine.wait_for_unit("misskey.service")
+      machine.wait_for_open_port(${toString port})
+      machine.succeed("curl --fail http://localhost:${toString port}/")
+    '';
+  }
+)
diff --git a/nixos/tests/morph-browser.nix b/nixos/tests/morph-browser.nix
index 859e6bb47646a..65ad4d85cc12e 100644
--- a/nixos/tests/morph-browser.nix
+++ b/nixos/tests/morph-browser.nix
@@ -23,7 +23,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
 
     fonts.packages = with pkgs; [
       # Intended font & helps with OCR
-      ubuntu_font_family
+      ubuntu-classic
     ];
   };
 
diff --git a/nixos/tests/mpd.nix b/nixos/tests/mpd.nix
index 52d9c7fd33a1b..0772c05d12ac4 100644
--- a/nixos/tests/mpd.nix
+++ b/nixos/tests/mpd.nix
@@ -37,7 +37,6 @@ import ./make-test-python.nix ({ pkgs, lib, ... }:
 
     mkServer = { mpd, musicService, }:
       { boot.kernelModules = [ "snd-dummy" ];
-        sound.enable = true;
         services.mpd = mpd;
         systemd.services.musicService = musicService;
       };
diff --git a/nixos/tests/music-assistant.nix b/nixos/tests/music-assistant.nix
new file mode 100644
index 0000000000000..ac667ee953035
--- /dev/null
+++ b/nixos/tests/music-assistant.nix
@@ -0,0 +1,21 @@
+{
+  lib,
+  ...
+}:
+
+{
+  name = "music-assistant";
+  meta.maintainers = with lib.maintainers; [ hexa ];
+
+  nodes.machine = {
+    services.music-assistant = {
+      enable = true;
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("music-assistant.service")
+    machine.wait_until_succeeds("curl --fail http://localhost:8095")
+    machine.log(machine.succeed("systemd-analyze security music-assistant.service | grep -v ✓"))
+  '';
+}
diff --git a/nixos/tests/mxisd.nix b/nixos/tests/mxisd.nix
deleted file mode 100644
index 354612a8a53d0..0000000000000
--- a/nixos/tests/mxisd.nix
+++ /dev/null
@@ -1,21 +0,0 @@
-import ./make-test-python.nix ({ pkgs, ... } : {
-
-  name = "mxisd";
-  meta = with pkgs.lib.maintainers; {
-    maintainers = [ mguentner ];
-  };
-
-  nodes = {
-    server = args : {
-      services.mxisd.enable = true;
-      services.mxisd.matrix.domain = "example.org";
-    };
-  };
-
-  testScript = ''
-    start_all()
-    server.wait_for_unit("mxisd.service")
-    server.wait_for_open_port(8090)
-    server.succeed("curl -Ssf 'http://127.0.0.1:8090/_matrix/identity/api/v1'")
-  '';
-})
diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix
index 8b682a8b3aa7c..550c5a2d14f31 100644
--- a/nixos/tests/nat.nix
+++ b/nixos/tests/nat.nix
@@ -31,7 +31,7 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ...
           lib.mkMerge [
             { virtualisation.vlans = [ 1 ];
               networking.defaultGateway =
-                (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
+                (pkgs.lib.head nodes.router.networking.interfaces.eth2.ipv4.addresses).address;
               networking.nftables.enable = nftables;
             }
           ];
@@ -61,8 +61,8 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ...
 
     testScript =
       { nodes, ... }: let
-        routerDummyNoNatClosure = nodes.routerDummyNoNat.config.system.build.toplevel;
-        routerClosure = nodes.router.config.system.build.toplevel;
+        routerDummyNoNatClosure = nodes.routerDummyNoNat.system.build.toplevel;
+        routerClosure = nodes.router.system.build.toplevel;
       in ''
         client.start()
         router.start()
@@ -72,13 +72,13 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ...
         server.wait_for_unit("network.target")
         server.wait_for_unit("httpd")
         router.wait_for_unit("network.target")
-        router.succeed("curl --fail http://server/ >&2")
+        router.succeed("curl -4 --fail http://server/ >&2")
 
         # The client should be also able to connect via the NAT router.
         router.wait_for_unit("${unit}")
         client.wait_for_unit("network.target")
         client.succeed("curl --fail http://server/ >&2")
-        client.succeed("ping -c 1 server >&2")
+        client.succeed("ping -4 -c 1 server >&2")
 
         # Test whether passive FTP works.
         server.wait_for_unit("vsftpd")
@@ -89,15 +89,15 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ...
         client.fail("curl -v -P - ftp://server/foo.txt >&2")
 
         # Test ICMP.
-        client.succeed("ping -c 1 router >&2")
-        router.succeed("ping -c 1 client >&2")
+        client.succeed("ping -4 -c 1 router >&2")
+        router.succeed("ping -4 -c 1 client >&2")
 
         # If we turn off NAT, the client shouldn't be able to reach the server.
         router.succeed(
             "${routerDummyNoNatClosure}/bin/switch-to-configuration test 2>&1"
         )
-        client.fail("curl --fail --connect-timeout 5 http://server/ >&2")
-        client.fail("ping -c 1 server >&2")
+        client.fail("curl -4 --fail --connect-timeout 5 http://server/ >&2")
+        client.fail("ping -4 -c 1 server >&2")
 
         # And make sure that reloading the NAT job works.
         router.succeed(
@@ -109,7 +109,7 @@ import ./make-test-python.nix ({ pkgs, lib, withFirewall, nftables ? false, ...
         ${lib.optionalString (!withFirewall && !nftables) ''
           router.succeed("systemctl start nat.service")
         ''}
-        client.succeed("curl --fail http://server/ >&2")
-        client.succeed("ping -c 1 server >&2")
+        client.succeed("curl -4 --fail http://server/ >&2")
+        client.succeed("ping -4 -c 1 server >&2")
       '';
-  })
+})
diff --git a/nixos/tests/networking-proxy.nix b/nixos/tests/networking-proxy.nix
index 330bac2588a5a..72f33c78bd0ec 100644
--- a/nixos/tests/networking-proxy.nix
+++ b/nixos/tests/networking-proxy.nix
@@ -12,7 +12,7 @@ let default-config = {
 in import ./make-test-python.nix ({ pkgs, ...} : {
   name = "networking-proxy";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [  ];
+    maintainers = [ ];
   };
 
   nodes = {
diff --git a/nixos/tests/networking/networkmanager.nix b/nixos/tests/networking/networkmanager.nix
index c8c44f9320d40..bd989408df8a1 100644
--- a/nixos/tests/networking/networkmanager.nix
+++ b/nixos/tests/networking/networkmanager.nix
@@ -166,7 +166,7 @@ let
 in lib.mapAttrs (lib.const (attrs: makeTest (attrs // {
   name = "${attrs.name}-Networking-NetworkManager";
   meta = {
-    maintainers = with lib.maintainers; [ ];
+    maintainers = [ ];
   };
 
 }))) testCases
diff --git a/nixos/tests/nixos-rebuild-specialisations.nix b/nixos/tests/nixos-rebuild-specialisations.nix
index 9192b8a8a030b..a5b916f7d7e90 100644
--- a/nixos/tests/nixos-rebuild-specialisations.nix
+++ b/nixos/tests/nixos-rebuild-specialisations.nix
@@ -71,6 +71,32 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         }
       '';
 
+      wrongConfigFile = pkgs.writeText "configuration.nix" ''
+        { lib, pkgs, ... }: {
+          imports = [
+            ./hardware-configuration.nix
+            <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          ];
+
+          boot.loader.grub = {
+            enable = true;
+            device = "/dev/vda";
+            forceInstall = true;
+          };
+
+          documentation.enable = false;
+
+          environment.systemPackages = [
+            (pkgs.writeShellScriptBin "parent" "")
+          ];
+
+          specialisation.foo-bar = {
+            inheritParentConfig = true;
+
+            configuration = { ... }: { };
+          };
+        }
+      '';
     in
     ''
       machine.start()
@@ -116,5 +142,12 @@ import ./make-test-python.nix ({ pkgs, ... }: {
       with subtest("Make sure nonsense command combinations are forbidden"):
           machine.fail("nixos-rebuild boot --specialisation foo")
           machine.fail("nixos-rebuild boot -c foo")
+
+      machine.copy_from_host(
+          "${wrongConfigFile}",
+          "/etc/nixos/configuration.nix",
+      )
+      with subtest("Make sure that invalid specialisation names are rejected"):
+          machine.fail("nixos-rebuild switch")
     '';
 })
diff --git a/nixos/tests/nvmetcfg.nix b/nixos/tests/nvmetcfg.nix
index a4c459a343cfd..169e5e9d7b0c9 100644
--- a/nixos/tests/nvmetcfg.nix
+++ b/nixos/tests/nvmetcfg.nix
@@ -27,7 +27,7 @@ import ./make-test-python.nix ({ lib, ... }: {
 
     with subtest("Bind subsystem to port"):
       server.wait_for_unit("network-online.target")
-      server.succeed("nvmet port add 1 tcp 0.0.0.0:4420")
+      server.succeed("nvmet port add 1 tcp [::]:4420")
       server.succeed("nvmet port add-subsystem 1 ${subsystem}")
 
     with subtest("Discover and connect to available subsystems"):
diff --git a/nixos/tests/nzbhydra2.nix b/nixos/tests/nzbhydra2.nix
index e1d528cd9520e..6262a50b4be0e 100644
--- a/nixos/tests/nzbhydra2.nix
+++ b/nixos/tests/nzbhydra2.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ lib, ... }:
   {
     name = "nzbhydra2";
-    meta.maintainers = with lib.maintainers; [ jamiemagee ];
+    meta.maintainers = with lib.maintainers; [ matteopacini ];
 
     nodes.machine = { pkgs, ... }: { services.nzbhydra2.enable = true; };
 
diff --git a/nixos/tests/odoo.nix b/nixos/tests/odoo.nix
index 45ec7b7d7a6b7..d3764cbc9f0b9 100644
--- a/nixos/tests/odoo.nix
+++ b/nixos/tests/odoo.nix
@@ -12,20 +12,10 @@ import ./make-test-python.nix ({ pkgs, lib, package ? pkgs.odoo, ...} : {
       services.odoo = {
         enable = true;
         package = package;
+        autoInit = true;
+        autoInitExtraFlags = [ "--without-demo=all" ];
         domain = "localhost";
       };
-
-      # odoo does not automatically initialize its database,
-      # even if passing what _should_ be the equivalent of these options:
-      #  settings = {
-      #    options = {
-      #      database = "odoo";
-      #      init = "base";
-      #    };
-      #  };
-      systemd.services.odoo.preStart = ''
-        HOME=$STATE_DIRECTORY ${package}/bin/odoo -d odoo -i base --stop-after-init --without-demo all
-      '';
     };
   };
 
diff --git a/nixos/tests/ollama-cuda.nix b/nixos/tests/ollama-cuda.nix
new file mode 100644
index 0000000000000..bbab7e24d35c7
--- /dev/null
+++ b/nixos/tests/ollama-cuda.nix
@@ -0,0 +1,17 @@
+{ lib, ... }:
+{
+  name = "ollama-cuda";
+  meta.maintainers = with lib.maintainers; [ abysssol ];
+
+  nodes.cuda =
+    { ... }:
+    {
+      services.ollama.enable = true;
+      services.ollama.acceleration = "cuda";
+    };
+
+  testScript = ''
+    cuda.wait_for_unit("multi-user.target")
+    cuda.wait_for_open_port(11434)
+  '';
+}
diff --git a/nixos/tests/ollama-rocm.nix b/nixos/tests/ollama-rocm.nix
new file mode 100644
index 0000000000000..81915630d950b
--- /dev/null
+++ b/nixos/tests/ollama-rocm.nix
@@ -0,0 +1,17 @@
+{ lib, ... }:
+{
+  name = "ollama-rocm";
+  meta.maintainers = with lib.maintainers; [ abysssol ];
+
+  nodes.rocm =
+    { ... }:
+    {
+      services.ollama.enable = true;
+      services.ollama.acceleration = "rocm";
+    };
+
+  testScript = ''
+    rocm.wait_for_unit("multi-user.target")
+    rocm.wait_for_open_port(11434)
+  '';
+}
diff --git a/nixos/tests/ollama.nix b/nixos/tests/ollama.nix
index 30e475553eb1a..34347716af726 100644
--- a/nixos/tests/ollama.nix
+++ b/nixos/tests/ollama.nix
@@ -1,56 +1,53 @@
-import ./make-test-python.nix ({ pkgs, lib, ... }:
+{ lib, ... }:
 let
   mainPort = 11434;
   altPort = 11435;
-
-  curlRequest = port: request:
-    "curl http://127.0.0.1:${toString port}/api/generate -d '${builtins.toJSON request}'";
-
-  prompt = {
-    model = "tinydolphin";
-    prompt = "lorem ipsum";
-    options = {
-      seed = 69;
-      temperature = 0;
-    };
-  };
 in
 {
   name = "ollama";
-  meta = with lib.maintainers; {
-    maintainers = [ abysssol ];
-  };
+  meta.maintainers = with lib.maintainers; [ abysssol ];
 
   nodes = {
-    cpu = { ... }: {
-      services.ollama.enable = true;
-    };
-
-    rocm = { ... }: {
-      services.ollama.enable = true;
-      services.ollama.acceleration = "rocm";
-    };
-
-    cuda = { ... }: {
-      services.ollama.enable = true;
-      services.ollama.acceleration = "cuda";
-    };
-
-    altAddress = { ... }: {
-      services.ollama.enable = true;
-      services.ollama.port = altPort;
-    };
+    cpu =
+      { ... }:
+      {
+        services.ollama.enable = true;
+      };
+
+    altAddress =
+      { ... }:
+      {
+        services.ollama.enable = true;
+        services.ollama.port = altPort;
+      };
   };
 
   testScript = ''
-    vms = [ cpu, rocm, cuda, altAddress ];
+    import json
 
-    start_all()
-    for vm in vms:
-        vm.wait_for_unit("multi-user.target")
+    def curl_request_ollama(prompt, port):
+      json_prompt = json.dumps(prompt)
+      return f"""curl http://127.0.0.1:{port}/api/generate -d '{json_prompt}'"""
+
+    prompt = {
+      "model": "tinydolphin",
+      "prompt": "lorem ipsum",
+      "options": {
+        "seed": 69,
+        "temperature": 0,
+      },
+    }
 
-    stdout = cpu.succeed("""${curlRequest mainPort prompt}""", timeout=100)
 
-    stdout = altAddress.succeed("""${curlRequest altPort prompt}""", timeout=100)
+    vms = [
+      (cpu, ${toString mainPort}),
+      (altAddress, ${toString altPort}),
+    ]
+
+    start_all()
+    for (vm, port) in vms:
+      vm.wait_for_unit("multi-user.target")
+      vm.wait_for_open_port(port)
+      stdout = vm.succeed(curl_request_ollama(prompt, port), timeout = 100)
   '';
-})
+}
diff --git a/nixos/tests/pam/pam-u2f.nix b/nixos/tests/pam/pam-u2f.nix
index 46e307a3f125a..caa56c30bbce9 100644
--- a/nixos/tests/pam/pam-u2f.nix
+++ b/nixos/tests/pam/pam-u2f.nix
@@ -7,12 +7,16 @@ import ../make-test-python.nix ({ ... }:
     { ... }:
     {
       security.pam.u2f = {
-        control = "required";
-        cue = true;
-        debug = true;
         enable = true;
-        interactive = true;
-        origin = "nixos-test";
+        control = "required";
+        settings = {
+          cue = true;
+          debug = true;
+          interactive = true;
+          origin = "nixos-test";
+          # Freeform option
+          userpresence = 1;
+        };
       };
     };
 
@@ -20,7 +24,7 @@ import ../make-test-python.nix ({ ... }:
     ''
       machine.wait_for_unit("multi-user.target")
       machine.succeed(
-          'egrep "auth required .*/lib/security/pam_u2f.so.*cue.*debug.*interactive.*origin=nixos-test" /etc/pam.d/ -R'
+          'egrep "auth required .*/lib/security/pam_u2f.so.*cue.*debug.*interactive.*origin=nixos-test.*userpresence=1" /etc/pam.d/ -R'
       )
     '';
 })
diff --git a/nixos/tests/pantheon.nix b/nixos/tests/pantheon.nix
index d2a4a009af53d..9806a1e3052ea 100644
--- a/nixos/tests/pantheon.nix
+++ b/nixos/tests/pantheon.nix
@@ -96,9 +96,8 @@ import ./make-test-python.nix ({ pkgs, lib, ...} :
         cmd = "dbus-send --session --dest=org.pantheon.gala --print-reply /org/pantheon/gala org.pantheon.gala.PerformAction int32:1"
         env = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${toString user.uid}/bus DISPLAY=:0"
         machine.succeed(f"su - ${user.name} -c '{env} {cmd}'")
-        machine.sleep(3)
+        machine.sleep(5)
         machine.screenshot("multitasking")
-        machine.succeed(f"su - ${user.name} -c '{env} {cmd}'")
 
     with subtest("Check if gala has ever coredumped"):
         machine.fail("coredumpctl --json=short | grep gala")
diff --git a/nixos/tests/postgresql-tls-client-cert.nix b/nixos/tests/postgresql-tls-client-cert.nix
new file mode 100644
index 0000000000000..c1678ed733beb
--- /dev/null
+++ b/nixos/tests/postgresql-tls-client-cert.nix
@@ -0,0 +1,141 @@
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../.. { inherit system config; }
+, package ? null
+}:
+
+with import ../lib/testing-python.nix { inherit system pkgs; };
+
+let
+  lib = pkgs.lib;
+
+  # Makes a test for a PostgreSQL package, given by name and looked up from `pkgs`.
+  makeTestAttribute = name:
+    {
+      inherit name;
+      value = makePostgresqlTlsClientCertTest pkgs."${name}";
+    };
+
+  makePostgresqlTlsClientCertTest = pkg:
+    let
+      runWithOpenSSL = file: cmd: pkgs.runCommand file
+        {
+          buildInputs = [ pkgs.openssl ];
+        }
+        cmd;
+      caKey = runWithOpenSSL "ca.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
+      caCert = runWithOpenSSL
+        "ca.crt"
+        ''
+          openssl req -new -x509 -sha256 -key ${caKey} -out $out -subj "/CN=test.example" -days 36500
+        '';
+      serverKey =
+        runWithOpenSSL "server.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
+      serverKeyPath = "/var/lib/postgresql";
+      serverCert =
+        runWithOpenSSL "server.crt" ''
+          openssl req -new -sha256 -key ${serverKey} -out server.csr -subj "/CN=db.test.example"
+          openssl x509 -req -in server.csr -CA ${caCert} -CAkey ${caKey} \
+            -CAcreateserial -out $out -days 36500 -sha256
+        '';
+      clientKey =
+        runWithOpenSSL "client.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
+      clientCert =
+        runWithOpenSSL "client.crt" ''
+          openssl req -new -sha256 -key ${clientKey} -out client.csr -subj "/CN=test"
+          openssl x509 -req -in client.csr -CA ${caCert} -CAkey ${caKey} \
+            -CAcreateserial -out $out -days 36500 -sha256
+        '';
+      clientKeyPath = "/root";
+
+    in
+    makeTest {
+      name = "postgresql-tls-client-cert-${pkg.name}";
+      meta.maintainers = with lib.maintainers; [ erictapen ];
+
+      nodes.server = { ... }: {
+        system.activationScripts = {
+          keyPlacement.text = ''
+            mkdir -p '${serverKeyPath}'
+            cp '${serverKey}' '${serverKeyPath}/server.key'
+            chown postgres:postgres '${serverKeyPath}/server.key'
+            chmod 600 '${serverKeyPath}/server.key'
+          '';
+        };
+        services.postgresql = {
+          package = pkg;
+          enable = true;
+          enableTCPIP = true;
+          ensureUsers = [
+            {
+              name = "test";
+              ensureDBOwnership = true;
+            }
+          ];
+          ensureDatabases = [ "test" ];
+          settings = {
+            ssl = "on";
+            ssl_ca_file = toString caCert;
+            ssl_cert_file = toString serverCert;
+            ssl_key_file = "${serverKeyPath}/server.key";
+          };
+          authentication = ''
+            hostssl test test ::/0 cert clientcert=verify-full
+          '';
+        };
+        networking = {
+          interfaces.eth1 = {
+            ipv6.addresses = [
+              { address = "fc00::1"; prefixLength = 120; }
+            ];
+          };
+          firewall.allowedTCPPorts = [ 5432 ];
+        };
+      };
+
+      nodes.client = { ... }: {
+        system.activationScripts = {
+          keyPlacement.text = ''
+            mkdir -p '${clientKeyPath}'
+            cp '${clientKey}' '${clientKeyPath}/client.key'
+            chown root:root '${clientKeyPath}/client.key'
+            chmod 600 '${clientKeyPath}/client.key'
+          '';
+        };
+        environment = {
+          variables = {
+            PGHOST = "db.test.example";
+            PGPORT = "5432";
+            PGDATABASE = "test";
+            PGUSER = "test";
+            PGSSLMODE = "verify-full";
+            PGSSLCERT = clientCert;
+            PGSSLKEY = "${clientKeyPath}/client.key";
+            PGSSLROOTCERT = caCert;
+          };
+          systemPackages = [ pkg ];
+        };
+        networking = {
+          interfaces.eth1 = {
+            ipv6.addresses = [
+              { address = "fc00::2"; prefixLength = 120; }
+            ];
+          };
+          hosts = { "fc00::1" = [ "db.test.example" ]; };
+        };
+      };
+
+      testScript = ''
+        server.wait_for_unit("multi-user.target")
+        client.wait_for_unit("multi-user.target")
+        client.succeed("psql -c \"SELECT 1;\"")
+      '';
+    };
+
+in
+if package == null then
+# all-tests.nix: Maps the generic function over all attributes of PostgreSQL packages
+  builtins.listToAttrs (map makeTestAttribute (builtins.attrNames (import ../../pkgs/servers/sql/postgresql pkgs)))
+else
+# Called directly from <package>.tests
+  makePostgresqlTlsClientCertTest package
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index d9a52fa89b1c9..fba6bd57d9c49 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -177,6 +177,26 @@ let
       '';
     };
 
+    borgmatic = {
+      exporterConfig = {
+        enable = true;
+        user = "root";
+      };
+      metricProvider = {
+        services.borgmatic.enable = true;
+        services.borgmatic.settings.source_directories = [ "/home" ];
+        services.borgmatic.settings.repositories = [ { label = "local"; path = "/var/backup"; } ];
+        services.borgmatic.settings.keep_daily = 10;
+      };
+      exporterTest = ''
+        succeed("borgmatic rcreate -e none")
+        succeed("borgmatic")
+        wait_for_unit("prometheus-borgmatic-exporter.service")
+        wait_for_open_port(9996)
+        succeed("curl -sSf localhost:9996/metrics | grep 'borg_total_backups{repository=\"/var/backup\"} 1'")
+      '';
+    };
+
     collectd = {
       exporterConfig = {
         enable = true;
@@ -209,6 +229,34 @@ let
         '';
     };
 
+    deluge = {
+      exporterConfig = {
+        enable = true;
+        port = 1234;
+        listenAddress = "127.0.0.1";
+
+        delugeUser = "user";
+        delugePort = 2345;
+        delugePasswordFile = pkgs.writeText "password" "weak_password";
+      };
+      metricProvider = {
+        services.deluge.enable = true;
+        services.deluge.declarative = true;
+        services.deluge.config.daemon_port = 2345;
+        services.deluge.authFile = pkgs.writeText "authFile" ''
+        localclient:abcdef:10
+        user:weak_password:10
+        '';
+      };
+      exporterTest = ''
+        wait_for_unit("deluged.service")
+        wait_for_open_port(2345)
+        wait_for_unit("prometheus-deluge-exporter.service")
+        wait_for_open_port(1234)
+        succeed("curl -sSf http://localhost:1234 | grep 'deluge_torrents'")
+      '';
+    };
+
     dnsmasq = {
       exporterConfig = {
         enable = true;
diff --git a/nixos/tests/prometheus/alertmanager.nix b/nixos/tests/prometheus/alertmanager.nix
index feda8d8fc2bcc..6301db6df62e3 100644
--- a/nixos/tests/prometheus/alertmanager.nix
+++ b/nixos/tests/prometheus/alertmanager.nix
@@ -144,5 +144,9 @@ import ../make-test-python.nix ({ lib, pkgs, ... }:
     logger.wait_until_succeeds(
       "journalctl -o cat -u alertmanager-webhook-logger.service | grep '\"alertname\":\"InstanceDown\"'"
     )
+
+    logger.log(logger.succeed("systemd-analyze security alertmanager-webhook-logger.service | grep -v '✓'"))
+
+    alertmanager.log(alertmanager.succeed("systemd-analyze security alertmanager.service | grep -v '✓'"))
   '';
 })
diff --git a/nixos/tests/prometheus/pushgateway.nix b/nixos/tests/prometheus/pushgateway.nix
index 7904c8bf45b04..261c41598eb02 100644
--- a/nixos/tests/prometheus/pushgateway.nix
+++ b/nixos/tests/prometheus/pushgateway.nix
@@ -90,5 +90,7 @@ import ../make-test-python.nix ({ lib, pkgs, ... }:
       "curl -sf 'http://127.0.0.1:9090/api/v1/query?query=absent(some_metric)' | "
       + "jq '.data.result[0].value[1]' | grep '\"1\"'"
     )
+
+    pushgateway.log(pushgateway.succeed("systemd-analyze security pushgateway.service | grep -v '✓'"))
   '';
 })
diff --git a/nixos/tests/pt2-clone.nix b/nixos/tests/pt2-clone.nix
index ea4329c4a9806..57a8495a3296a 100644
--- a/nixos/tests/pt2-clone.nix
+++ b/nixos/tests/pt2-clone.nix
@@ -10,7 +10,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     ];
 
     services.xserver.enable = true;
-    sound.enable = true;
     environment.systemPackages = [ pkgs.pt2-clone ];
   };
 
diff --git a/nixos/tests/qemu-vm-store.nix b/nixos/tests/qemu-vm-store.nix
new file mode 100644
index 0000000000000..9fb9f4baaafc4
--- /dev/null
+++ b/nixos/tests/qemu-vm-store.nix
@@ -0,0 +1,71 @@
+{ lib, ... }: {
+
+  name = "qemu-vm-store";
+
+  meta.maintainers = with lib.maintainers; [ nikstur ];
+
+  nodes = {
+    sharedWritable = {
+      virtualisation.writableStore = true;
+    };
+
+    sharedReadOnly = {
+      virtualisation.writableStore = false;
+    };
+
+    imageWritable = {
+      virtualisation.useNixStoreImage = true;
+      virtualisation.writableStore = true;
+    };
+
+    imageReadOnly = {
+      virtualisation.useNixStoreImage = true;
+      virtualisation.writableStore = false;
+    };
+
+    fullDisk = {
+      virtualisation.useBootLoader = true;
+    };
+  };
+
+  testScript = ''
+    build_derivation = """
+      nix-build --option substitute false -E 'derivation {
+        name = "t";
+        builder = "/bin/sh";
+        args = ["-c" "echo something > $out"];
+        system = builtins.currentSystem;
+        preferLocalBuild = true;
+      }'
+    """
+
+    start_all()
+
+    with subtest("Nix Store is writable"):
+      sharedWritable.succeed(build_derivation)
+      imageWritable.succeed(build_derivation)
+      fullDisk.succeed(build_derivation)
+
+    with subtest("Nix Store is read only"):
+      sharedReadOnly.fail(build_derivation)
+      imageReadOnly.fail(build_derivation)
+
+    # Checking whether the fs type is 9P is just a proxy to test whether the
+    # Nix Store is shared. If we switch to a different technology (e.g.
+    # virtiofs) for sharing, we need to adjust these tests.
+
+    with subtest("Nix store is shared from the host via 9P"):
+      sharedWritable.succeed("findmnt --kernel --type 9P /nix/.ro-store")
+      sharedReadOnly.succeed("findmnt --kernel --type 9P /nix/.ro-store")
+
+    with subtest("Nix store is not shared via 9P"):
+      imageWritable.fail("findmnt --kernel --type 9P /nix/.ro-store")
+      imageReadOnly.fail("findmnt --kernel --type 9P /nix/.ro-store")
+
+    with subtest("Nix store is not mounted separately"):
+      rootDevice = fullDisk.succeed("stat -c %d /")
+      nixStoreDevice = fullDisk.succeed("stat -c %d /nix/store")
+      assert rootDevice == nixStoreDevice, "Nix store is mounted separately from the root fs"
+  '';
+
+}
diff --git a/nixos/tests/quake3.nix b/nixos/tests/quake3.nix
index ff4025e56f4c4..947476c7ebc1a 100644
--- a/nixos/tests/quake3.nix
+++ b/nixos/tests/quake3.nix
@@ -65,8 +65,8 @@ rec {
       client1.wait_for_x()
       client2.wait_for_x()
 
-      client1.execute("quake3 +set r_fullscreen 0 +set name Foo +connect server &")
-      client2.execute("quake3 +set r_fullscreen 0 +set name Bar +connect server &")
+      client1.execute("quake3 +set r_fullscreen 0 +set name Foo +connect server >&2 &", check_return = False)
+      client2.execute("quake3 +set r_fullscreen 0 +set name Bar +connect server >&2 &", check_return = False)
 
       server.wait_until_succeeds("grep -q 'Foo.*entered the game' /tmp/log")
       server.wait_until_succeeds("grep -q 'Bar.*entered the game' /tmp/log")
diff --git a/nixos/tests/radicle.nix b/nixos/tests/radicle.nix
new file mode 100644
index 0000000000000..b68cb7d716c23
--- /dev/null
+++ b/nixos/tests/radicle.nix
@@ -0,0 +1,207 @@
+# This test runs the radicle-node and radicle-httpd services on a seed host,
+# and verifies that an alice peer can host a repository on the seed,
+# and that a bob peer can send alice a patch via the seed.
+
+{ pkgs, ... }:
+
+let
+  # The Node ID depends on nodes.seed.services.radicle.privateKeyFile
+  seed-nid = "z6Mkg52RcwDrPKRzzHaYgBkHH3Gi5p4694fvPstVE9HTyMB6";
+  seed-ssh-keys = import ./ssh-keys.nix pkgs;
+  seed-tls-certs = import common/acme/server/snakeoil-certs.nix;
+
+  commonHostConfig = { nodes, config, pkgs, ... }: {
+    environment.systemPackages = [
+      config.services.radicle.package
+      pkgs.curl
+      pkgs.gitMinimal
+      pkgs.jq
+    ];
+    environment.etc."gitconfig".text = ''
+      [init]
+        defaultBranch = main
+      [user]
+        email = root@${config.networking.hostName}
+        name = ${config.networking.hostName}
+    '';
+    networking = {
+      extraHosts = ''
+        ${nodes.seed.networking.primaryIPAddress} ${nodes.seed.services.radicle.httpd.nginx.serverName}
+      '';
+    };
+    security.pki.certificateFiles = [
+      seed-tls-certs.ca.cert
+    ];
+  };
+
+  radicleConfig = { nodes, ... }: alias:
+    pkgs.writeText "config.json" (builtins.toJSON {
+      preferredSeeds = [
+        "${seed-nid}@seed:${toString nodes.seed.services.radicle.node.listenPort}"
+      ];
+      node = {
+        inherit alias;
+        relay = "never";
+        seedingPolicy = {
+          default = "block";
+        };
+      };
+    });
+in
+
+{
+  name = "radicle";
+
+  meta = with pkgs.lib.maintainers; {
+    maintainers = [
+      julm
+      lorenzleutgeb
+    ];
+  };
+
+  nodes = {
+    seed = { pkgs, config, ... }: {
+      imports = [ commonHostConfig ];
+
+      services.radicle = {
+        enable = true;
+        privateKeyFile = seed-ssh-keys.snakeOilEd25519PrivateKey;
+        publicKey = seed-ssh-keys.snakeOilEd25519PublicKey;
+        node = {
+          openFirewall = true;
+        };
+        httpd = {
+          enable = true;
+          nginx = {
+            serverName = seed-tls-certs.domain;
+            addSSL = true;
+            sslCertificate = seed-tls-certs.${seed-tls-certs.domain}.cert;
+            sslCertificateKey = seed-tls-certs.${seed-tls-certs.domain}.key;
+          };
+        };
+        settings = {
+          preferredSeeds = [];
+          node = {
+            relay = "always";
+            seedingPolicy = {
+              default = "allow";
+              scope = "all";
+            };
+          };
+        };
+      };
+
+      services.nginx = {
+        enable = true;
+      };
+
+      networking.firewall.allowedTCPPorts = [ 443 ];
+    };
+
+    alice = {
+      imports = [ commonHostConfig ];
+    };
+
+    bob = {
+      imports = [ commonHostConfig ];
+    };
+  };
+
+  testScript = { nodes, ... }@args: ''
+    start_all()
+
+    with subtest("seed can run radicle-node"):
+      # The threshold and/or hardening may have to be changed with new features/checks
+      print(seed.succeed("systemd-analyze security radicle-node.service --threshold=10 --no-pager"))
+      seed.wait_for_unit("radicle-node.service")
+      seed.wait_for_open_port(${toString nodes.seed.services.radicle.node.listenPort})
+
+    with subtest("seed can run radicle-httpd"):
+      # The threshold and/or hardening may have to be changed with new features/checks
+      print(seed.succeed("systemd-analyze security radicle-httpd.service --threshold=10 --no-pager"))
+      seed.wait_for_unit("radicle-httpd.service")
+      seed.wait_for_open_port(${toString nodes.seed.services.radicle.httpd.listenPort})
+      seed.wait_for_open_port(443)
+      assert alice.succeed("curl -sS 'https://${nodes.seed.services.radicle.httpd.nginx.serverName}/api/v1' | jq -r .nid") == "${seed-nid}\n"
+      assert bob.succeed("curl -sS 'https://${nodes.seed.services.radicle.httpd.nginx.serverName}/api/v1' | jq -r .nid") == "${seed-nid}\n"
+
+    with subtest("alice can create a Node ID"):
+      alice.succeed("rad auth --alias alice --stdin </dev/null")
+      alice.copy_from_host("${radicleConfig args "alice"}", "/root/.radicle/config.json")
+    with subtest("alice can run a node"):
+      alice.succeed("rad node start")
+    with subtest("alice can create a Git repository"):
+      alice.succeed(
+        "mkdir /tmp/repo",
+        "git -C /tmp/repo init",
+        "echo hello world > /tmp/repo/testfile",
+        "git -C /tmp/repo add .",
+        "git -C /tmp/repo commit -m init"
+      )
+    with subtest("alice can create a Repository ID"):
+      alice.succeed(
+        "cd /tmp/repo && rad init --name repo --description descr --default-branch main --public"
+      )
+    alice_repo_rid=alice.succeed("cd /tmp/repo && rad inspect --rid").rstrip("\n")
+    with subtest("alice can send a repository to the seed"):
+      alice.succeed(f"rad sync --seed ${seed-nid} {alice_repo_rid}")
+
+    with subtest(f"seed can receive the repository {alice_repo_rid}"):
+      seed.wait_until_succeeds("test 1 = \"$(rad-system stats | jq .local.repos)\"")
+
+    with subtest("bob can create a Node ID"):
+      bob.succeed("rad auth --alias bob --stdin </dev/null")
+      bob.copy_from_host("${radicleConfig args "bob"}", "/root/.radicle/config.json")
+      bob.succeed("rad node start")
+    with subtest("bob can clone alice's repository from the seed"):
+      bob.succeed(f"rad clone {alice_repo_rid} /tmp/repo")
+      assert bob.succeed("cat /tmp/repo/testfile") == "hello world\n"
+
+    with subtest("bob can clone alice's repository from the seed through the HTTP gateway"):
+      bob.succeed(f"git clone https://${nodes.seed.services.radicle.httpd.nginx.serverName}/{alice_repo_rid[4:]}.git /tmp/repo-http")
+      assert bob.succeed("cat /tmp/repo-http/testfile") == "hello world\n"
+
+    with subtest("alice can push the main branch to the rad remote"):
+      alice.succeed(
+        "echo hello bob > /tmp/repo/testfile",
+        "git -C /tmp/repo add .",
+        "git -C /tmp/repo commit -m 'hello to bob'",
+        "git -C /tmp/repo push rad main"
+      )
+    with subtest("bob can sync bob's repository from the seed"):
+      bob.succeed(
+        "cd /tmp/repo && rad sync --seed ${seed-nid}",
+        "cd /tmp/repo && git pull"
+      )
+      assert bob.succeed("cat /tmp/repo/testfile") == "hello bob\n"
+
+    with subtest("bob can push a patch"):
+      bob.succeed(
+        "echo hello alice > /tmp/repo/testfile",
+        "git -C /tmp/repo checkout -b for-alice",
+        "git -C /tmp/repo add .",
+        "git -C /tmp/repo commit -m 'hello to alice'",
+        "git -C /tmp/repo push -o patch.message='hello for alice' rad HEAD:refs/patches"
+      )
+
+    bob_repo_patch1_pid=bob.succeed("cd /tmp/repo && git branch --remotes | sed -ne 's:^ *rad/patches/::'p").rstrip("\n")
+    with subtest("alice can receive the patch"):
+      alice.wait_until_succeeds("test 1 = \"$(rad stats | jq .local.patches)\"")
+      alice.succeed(
+        f"cd /tmp/repo && rad patch show {bob_repo_patch1_pid} | grep 'opened by bob'",
+        f"cd /tmp/repo && rad patch checkout {bob_repo_patch1_pid}"
+      )
+      assert alice.succeed("cat /tmp/repo/testfile") == "hello alice\n"
+    with subtest("alice can comment the patch"):
+      alice.succeed(
+        f"cd /tmp/repo && rad patch comment {bob_repo_patch1_pid} -m thank-you"
+      )
+    with subtest("alice can merge the patch"):
+      alice.succeed(
+        "git -C /tmp/repo checkout main",
+        f"git -C /tmp/repo merge patch/{bob_repo_patch1_pid[:7]}",
+        "git -C /tmp/repo push rad main",
+        "cd /tmp/repo && rad patch list | grep -qxF 'Nothing to show.'"
+      )
+  '';
+}
diff --git a/nixos/tests/rathole.nix b/nixos/tests/rathole.nix
new file mode 100644
index 0000000000000..56d7a0129f803
--- /dev/null
+++ b/nixos/tests/rathole.nix
@@ -0,0 +1,89 @@
+import ./make-test-python.nix (
+  { pkgs, lib, ... }:
+
+  let
+    successMessage = "Success 3333115147933743662";
+  in
+  {
+    name = "rathole";
+    meta.maintainers = with lib.maintainers; [ xokdvium ];
+    nodes = {
+      server = {
+        networking = {
+          useNetworkd = true;
+          useDHCP = false;
+          firewall.enable = false;
+        };
+
+        systemd.network.networks."01-eth1" = {
+          name = "eth1";
+          networkConfig.Address = "10.0.0.1/24";
+        };
+
+        services.rathole = {
+          enable = true;
+          role = "server";
+          settings = {
+            server = {
+              bind_addr = "0.0.0.0:2333";
+              services = {
+                success-message = {
+                  bind_addr = "0.0.0.0:80";
+                  token = "hunter2";
+                };
+              };
+            };
+          };
+        };
+      };
+
+      client = {
+        networking = {
+          useNetworkd = true;
+          useDHCP = false;
+        };
+
+        systemd.network.networks."01-eth1" = {
+          name = "eth1";
+          networkConfig.Address = "10.0.0.2/24";
+        };
+
+        services.nginx = {
+          enable = true;
+          virtualHosts."127.0.0.1" = {
+            root = pkgs.writeTextDir "success-message.txt" successMessage;
+          };
+        };
+
+        services.rathole = {
+          enable = true;
+          role = "client";
+          credentialsFile = pkgs.writeText "rathole-credentials.toml" ''
+            [client.services.success-message]
+            token = "hunter2"
+          '';
+          settings = {
+            client = {
+              remote_addr = "10.0.0.1:2333";
+              services.success-message = {
+                local_addr = "127.0.0.1:80";
+              };
+            };
+          };
+        };
+      };
+    };
+
+    testScript = ''
+      start_all()
+      server.wait_for_unit("rathole.service")
+      server.wait_for_open_port(2333)
+      client.wait_for_unit("rathole.service")
+      server.wait_for_open_port(80)
+      response = server.succeed("curl http://127.0.0.1/success-message.txt")
+      assert "${successMessage}" in response, "Got invalid response"
+      response = client.succeed("curl http://10.0.0.1/success-message.txt")
+      assert "${successMessage}" in response, "Got invalid response"
+    '';
+  }
+)
diff --git a/nixos/tests/realm.nix b/nixos/tests/realm.nix
new file mode 100644
index 0000000000000..b39b0e0a161c7
--- /dev/null
+++ b/nixos/tests/realm.nix
@@ -0,0 +1,39 @@
+import ./make-test-python.nix ({ lib, pkgs, ... }: {
+  name = "realm";
+
+  meta = {
+    maintainers = with lib.maintainers; [ ocfox ];
+  };
+
+  nodes.machine = { pkgs, ... }: {
+    services.nginx = {
+      enable = true;
+      statusPage = true;
+    };
+    # realm need DNS resolv server to run or use config.dns.nameserver
+    services.resolved.enable = true;
+
+    services.realm = {
+      enable = true;
+      config = {
+        endpoints = [
+          {
+            listen = "0.0.0.0:1000";
+            remote = "127.0.0.1:80";
+          }
+        ];
+      };
+    };
+  };
+
+  testScript = ''
+    machine.wait_for_unit("nginx.service")
+    machine.wait_for_unit("realm.service")
+
+    machine.wait_for_open_port(80)
+    machine.wait_for_open_port(1000)
+
+    machine.succeed("curl --fail http://localhost:1000/")
+  '';
+
+})
diff --git a/nixos/tests/redlib.nix b/nixos/tests/redlib.nix
index e4bde25e30a63..808f857aed196 100644
--- a/nixos/tests/redlib.nix
+++ b/nixos/tests/redlib.nix
@@ -3,7 +3,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   meta.maintainers = with lib.maintainers; [ soispha ];
 
   nodes.machine = {
-    services.libreddit = {
+    services.redlib = {
       package = pkgs.redlib;
       enable = true;
       # Test CAP_NET_BIND_SERVICE
@@ -12,7 +12,7 @@ import ./make-test-python.nix ({ lib, pkgs, ... }: {
   };
 
   testScript = ''
-    machine.wait_for_unit("libreddit.service")
+    machine.wait_for_unit("redlib.service")
     machine.wait_for_open_port(80)
     # Query a page that does not require Internet access
     machine.succeed("curl --fail http://localhost:80/settings")
diff --git a/nixos/tests/restic.nix b/nixos/tests/restic.nix
index 4111720cf6be8..49631d27ca801 100644
--- a/nixos/tests/restic.nix
+++ b/nixos/tests/restic.nix
@@ -4,6 +4,7 @@ import ./make-test-python.nix (
   let
     remoteRepository = "/root/restic-backup";
     remoteFromFileRepository = "/root/restic-backup-from-file";
+    remoteInhibitTestRepository = "/root/restic-backup-inhibit-test";
     remoteNoInitRepository = "/root/restic-backup-no-init";
     rcloneRepository = "rclone:local:/root/restic-rclone-backup";
 
@@ -26,6 +27,7 @@ import ./make-test-python.nix (
         echo some_other_file > $out/some_other_file
         mkdir $out/a_dir
         echo a_file > $out/a_dir/a_file
+        echo a_file_2 > $out/a_dir/a_file_2
       '';
     };
 
@@ -61,11 +63,20 @@ import ./make-test-python.nix (
               inherit passwordFile exclude pruneOpts;
               initialize = true;
               repositoryFile = pkgs.writeText "repositoryFile" remoteFromFileRepository;
-              paths = [ "/opt/a_dir" ];
+              paths = [
+                "/opt/a_dir/a_file"
+                "/opt/a_dir/a_file_2"
+              ];
               dynamicFilesFrom = ''
                 find /opt -mindepth 1 -maxdepth 1 ! -name a_dir # all files in /opt except for a_dir
               '';
             };
+            inhibit-test = {
+              inherit passwordFile paths exclude pruneOpts;
+              repository = remoteInhibitTestRepository;
+              initialize = true;
+              inhibitsSleep = true;
+            };
             remote-noinit-backup = {
               inherit passwordFile exclude pruneOpts paths;
               initialize = false;
@@ -137,15 +148,18 @@ import ./make-test-python.nix (
           # test that remote-from-file-backup produces a snapshot
           "systemctl start restic-backups-remote-from-file-backup.service",
           'restic-remote-from-file-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
+          "mkdir /tmp/restore-2",
+          "restic-remote-from-file-backup restore latest -t /tmp/restore-2",
+          "diff -ru ${testDir} /tmp/restore-2/opt",
 
           # test that remote-noinit-backup produces a snapshot
           "systemctl start restic-backups-remote-noinit-backup.service",
           'restic-remote-noinit-backup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
 
           # test that restoring that snapshot produces the same directory
-          "mkdir /tmp/restore-2",
-          "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} restore latest -t /tmp/restore-2",
-          "diff -ru ${testDir} /tmp/restore-2/opt",
+          "mkdir /tmp/restore-3",
+          "${pkgs.restic}/bin/restic -r ${remoteRepository} -p ${passwordFile} restore latest -t /tmp/restore-3",
+          "diff -ru ${testDir} /tmp/restore-3/opt",
 
           # test that rclonebackup produces a snapshot
           "systemctl start restic-backups-rclonebackup.service",
@@ -190,6 +204,13 @@ import ./make-test-python.nix (
           'restic-remotebackup snapshots --json | ${pkgs.jq}/bin/jq "length | . == 1"',
 
       )
+
+      # test that the inhibit option is working
+      server.systemctl("start --no-block restic-backups-inhibit-test.service")
+      server.wait_until_succeeds(
+          "systemd-inhibit --no-legend --no-pager | grep -q restic",
+          5
+      )
     '';
   }
 )
diff --git a/nixos/tests/rosenpass.nix b/nixos/tests/rosenpass.nix
index 8765fd201c0e5..5ef6e55f53746 100644
--- a/nixos/tests/rosenpass.nix
+++ b/nixos/tests/rosenpass.nix
@@ -44,7 +44,8 @@ in
           enable = true;
           networks."rosenpass" = {
             matchConfig.Name = deviceName;
-            networkConfig.IPForward = true;
+            networkConfig.IPv4Forwarding = true;
+            networkConfig.IPv6Forwarding = true;
             address = [ "${peer.ip}/64" ];
           };
 
diff --git a/nixos/tests/sfxr-qt.nix b/nixos/tests/sfxr-qt.nix
index 976b9b11fc66a..cca3e5f3ea765 100644
--- a/nixos/tests/sfxr-qt.nix
+++ b/nixos/tests/sfxr-qt.nix
@@ -10,7 +10,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     ];
 
     services.xserver.enable = true;
-    sound.enable = true;
     environment.systemPackages = [ pkgs.sfxr-qt ];
   };
 
diff --git a/nixos/tests/shattered-pixel-dungeon.nix b/nixos/tests/shattered-pixel-dungeon.nix
index b4ac1670b5cad..cabf192c6002f 100644
--- a/nixos/tests/shattered-pixel-dungeon.nix
+++ b/nixos/tests/shattered-pixel-dungeon.nix
@@ -10,7 +10,6 @@ import ./make-test-python.nix ({ pkgs, ... }: {
     ];
 
     services.xserver.enable = true;
-    sound.enable = true;
     environment.systemPackages = [ pkgs.shattered-pixel-dungeon ];
   };
 
diff --git a/nixos/tests/slimserver.nix b/nixos/tests/slimserver.nix
index 95cbdcf4a2a15..abc0cd2ef1812 100644
--- a/nixos/tests/slimserver.nix
+++ b/nixos/tests/slimserver.nix
@@ -8,8 +8,7 @@ import ./make-test-python.nix ({ pkgs, ...} : {
       enable = true;
       extraArguments = "-s 127.0.0.1 -d slimproto=info";
     };
-    sound.enable = true;
-    boot.initrd.kernelModules = ["snd-dummy"];
+    boot.kernelModules = ["snd-dummy"];
   };
 
   testScript =
diff --git a/nixos/tests/sogo.nix b/nixos/tests/sogo.nix
index e9059a2ab7734..84d219659bdb2 100644
--- a/nixos/tests/sogo.nix
+++ b/nixos/tests/sogo.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, ... }: {
   name = "sogo";
   meta = with pkgs.lib.maintainers; {
-    maintainers = [];
+    maintainers = [ ];
   };
 
   nodes = {
diff --git a/nixos/tests/soju.nix b/nixos/tests/soju.nix
index 23da36f7b3aba..f13c447fb5f6b 100644
--- a/nixos/tests/soju.nix
+++ b/nixos/tests/soju.nix
@@ -8,7 +8,7 @@ let
 in
 {
   name = "soju";
-  meta.maintainers = with lib.maintainers; [ Benjamin-L ];
+  meta.maintainers = [ ];
 
   nodes.machine = { ... }: {
     services.soju = {
diff --git a/nixos/tests/step-ca.nix b/nixos/tests/step-ca.nix
index 184c35f6b85cc..68364e278d568 100644
--- a/nixos/tests/step-ca.nix
+++ b/nixos/tests/step-ca.nix
@@ -16,7 +16,7 @@ import ./make-test-python.nix ({ pkgs, ... }:
           { config, pkgs, ... }: {
             services.step-ca = {
               enable = true;
-              address = "0.0.0.0";
+              address = "[::]";
               port = 8443;
               openFirewall = true;
               intermediatePasswordFile = "${test-certificates}/intermediate-password-file";
diff --git a/nixos/tests/systemd-analyze.nix b/nixos/tests/systemd-analyze.nix
index 31588e2b41aa5..37c20d5fe5b65 100644
--- a/nixos/tests/systemd-analyze.nix
+++ b/nixos/tests/systemd-analyze.nix
@@ -9,7 +9,6 @@ import ./make-test-python.nix ({ pkgs, latestKernel ? false, ... }:
   nodes.machine =
     { pkgs, lib, ... }:
     { boot.kernelPackages = lib.mkIf latestKernel pkgs.linuxPackages_latest;
-      sound.enable = true; # needed for the factl test, /dev/snd/* exists without them but udev doesn't care then
     };
 
   testScript = ''
diff --git a/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch b/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch
deleted file mode 100644
index ef547c02f9187..0000000000000
--- a/nixos/tests/systemd-boot-ovmf-broken-fat-driver.patch
+++ /dev/null
@@ -1,25 +0,0 @@
-From d87a7513c6f2f2824203032ef27caeb84892ed7e Mon Sep 17 00:00:00 2001
-From: Will Fancher <elvishjerricco@gmail.com>
-Date: Tue, 30 May 2023 16:53:20 -0400
-Subject: [PATCH] Intentionally break the fat driver
-
----
- FatPkg/EnhancedFatDxe/ReadWrite.c | 5 +++++
- 1 file changed, 5 insertions(+)
-
-diff --git a/FatPkg/EnhancedFatDxe/ReadWrite.c b/FatPkg/EnhancedFatDxe/ReadWrite.c
-index 8f525044d1f1..32c62ff7817b 100644
---- a/FatPkg/EnhancedFatDxe/ReadWrite.c
-+++ b/FatPkg/EnhancedFatDxe/ReadWrite.c
-@@ -216,6 +216,11 @@ FatIFileAccess (
-   Volume = OFile->Volume;

-   Task   = NULL;

- 

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

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

-+    return EFI_BAD_BUFFER_SIZE;

-+  }

-+

-   //

-   // Write to a directory is unsupported

-   //

diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix
index 54c380602bd40..a9f42900addd0 100644
--- a/nixos/tests/systemd-boot.nix
+++ b/nixos/tests/systemd-boot.nix
@@ -13,6 +13,8 @@ let
     boot.loader.systemd-boot.enable = true;
     boot.loader.efi.canTouchEfiVariables = true;
     environment.systemPackages = [ pkgs.efibootmgr ];
+    # Needed for machine-id to be persisted between reboots
+    environment.etc."machine-id".text = "00000000000000000000000000000000";
   };
 
   commonXbootldr = { config, lib, pkgs, ... }:
@@ -81,7 +83,7 @@ let
     os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
   '';
 in
-{
+rec {
   basic = makeTest {
     name = "systemd-boot";
     meta.maintainers = with pkgs.lib.maintainers; [ danielfullmer julienmalka ];
@@ -93,7 +95,8 @@ in
       machine.wait_for_unit("multi-user.target")
 
       machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
-      machine.succeed("grep 'sort-key nixos' /boot/loader/entries/nixos-generation-1.conf")
+      # our sort-key will uses r to sort before nixos
+      machine.succeed("grep 'sort-key nixor-default' /boot/loader/entries/nixos-generation-1.conf")
 
       # Ensure we actually booted using systemd-boot
       # Magic number is the vendor UUID used by systemd-boot.
@@ -169,10 +172,23 @@ in
       imports = [ common ];
       specialisation.something.configuration = {
         boot.loader.systemd-boot.sortKey = "something";
+
+        # Since qemu will dynamically create a devicetree blob when starting
+        # up, it is not straight forward to create an export of that devicetree
+        # blob without knowing before-hand all the flags we would pass to qemu
+        # (we would then be able to use `dumpdtb`). Thus, the following config
+        # will not boot, but it does allow us to assert that the boot entry has
+        # the correct contents.
+        boot.loader.systemd-boot.installDeviceTree = pkgs.stdenv.hostPlatform.isAarch64;
+        hardware.deviceTree.name = "dummy.dtb";
+        hardware.deviceTree.package = lib.mkForce (pkgs.runCommand "dummy-devicetree-package" { } ''
+          mkdir -p $out
+          cp ${pkgs.emptyFile} $out/dummy.dtb
+        '');
       };
     };
 
-    testScript = ''
+    testScript = { nodes, ... }: ''
       machine.start()
       machine.wait_for_unit("multi-user.target")
 
@@ -185,6 +201,10 @@ in
       machine.succeed(
           "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
       )
+    '' + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 ''
+      machine.succeed(
+          "grep 'devicetree .*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf"
+      )
     '';
   };
 
@@ -232,14 +252,16 @@ in
       """
       )
 
-      output = machine.succeed("/run/current-system/bin/switch-to-configuration boot")
+      output = machine.succeed("/run/current-system/bin/switch-to-configuration boot 2>&1")
       assert "updating systemd-boot from 000.0-1-notnixos to " in output, "Couldn't find systemd-boot update message"
+      assert 'to "/boot/EFI/systemd/systemd-bootx64.efi"' in output, "systemd-boot not copied to to /boot/EFI/systemd/systemd-bootx64.efi"
+      assert 'to "/boot/EFI/BOOT/BOOTX64.EFI"' in output, "systemd-boot not copied to to /boot/EFI/BOOT/BOOTX64.EFI"
     '';
   };
 
   memtest86 = makeTest {
     name = "systemd-boot-memtest86";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -254,7 +276,7 @@ in
 
   netbootxyz = makeTest {
     name = "systemd-boot-netbootxyz";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -269,7 +291,7 @@ in
 
   memtestSortKey = makeTest {
     name = "systemd-boot-memtest-sortkey";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -307,7 +329,7 @@ in
 
   extraEntries = makeTest {
     name = "systemd-boot-extra-entries";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -326,7 +348,7 @@ in
 
   extraFiles = makeTest {
     name = "systemd-boot-extra-files";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
 
     nodes.machine = { pkgs, lib, ... }: {
       imports = [ common ];
@@ -343,7 +365,7 @@ in
 
   switch-test = makeTest {
     name = "systemd-boot-switch-test";
-    meta.maintainers = with pkgs.lib.maintainers; [ Enzime julienmalka ];
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
 
     nodes = {
       inherit common;
@@ -399,15 +421,15 @@ in
     '';
   };
 
-  garbage-collect-entry = makeTest {
-    name = "systemd-boot-garbage-collect-entry";
+  garbage-collect-entry = { withBootCounting ? false, ... }: makeTest {
+    name = "systemd-boot-garbage-collect-entry" + optionalString withBootCounting "-with-boot-counting";
     meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
 
     nodes = {
       inherit common;
       machine = { pkgs, nodes, ... }: {
         imports = [ common ];
-
+        boot.loader.systemd-boot.bootCounting.enable = withBootCounting;
         # These are configs for different nodes, but we'll use them here in `machine`
         system.extraDependencies = [
           nodes.common.system.build.toplevel
@@ -422,38 +444,16 @@ in
       ''
         machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${baseSystem}")
         machine.succeed("nix-env -p /nix/var/nix/profiles/system --delete-generations 1")
+        # At this point generation 1 has already been marked as good so we reintroduce counters artificially
+        ${optionalString withBootCounting ''
+        machine.succeed("mv /boot/loader/entries/nixos-generation-1.conf /boot/loader/entries/nixos-generation-1+3.conf")
+        ''}
         machine.succeed("${baseSystem}/bin/switch-to-configuration boot")
-        machine.fail("test -e /boot/loader/entries/nixos-generation-1.conf")
+        machine.fail("test -e /boot/loader/entries/nixos-generation-1*")
         machine.succeed("test -e /boot/loader/entries/nixos-generation-2.conf")
       '';
   };
 
-  # Some UEFI firmwares fail on large reads. Now that systemd-boot loads initrd
-  # itself, systems with such firmware won't boot without this fix
-  uefiLargeFileWorkaround = makeTest {
-    name = "uefi-large-file-workaround";
-    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
-    nodes.machine = { pkgs, ... }: {
-      imports = [common];
-      virtualisation.efi.OVMF = pkgs.OVMF.overrideAttrs (old: {
-        # This patch deliberately breaks the FAT driver in EDK2 to
-        # exhibit (part of) the firmware bug that we are testing
-        # for. Files greater than 10MiB will fail to be read in a
-        # single Read() call, so systemd-boot will fail to load the
-        # initrd without a workaround. The number 10MiB was chosen
-        # because if it were smaller than the kernel size, even the
-        # LoadImage call would fail, which is not the failure mode
-        # we're testing for. It needs to be between the kernel size
-        # and the initrd size.
-        patches = old.patches or [] ++ [ ./systemd-boot-ovmf-broken-fat-driver.patch ];
-      });
-    };
-
-    testScript = ''
-      machine.wait_for_unit("multi-user.target")
-    '';
-  };
-
   no-bootspec = makeTest
     {
       name = "systemd-boot-no-bootspec";
@@ -469,4 +469,138 @@ in
         machine.wait_for_unit("multi-user.target")
       '';
     };
+
+  # Check that we are booting the default entry and not the generation with largest version number
+  defaultEntry = { withBootCounting ? false, ... }: makeTest {
+    name = "systemd-boot-default-entry" + optionalString withBootCounting "-with-boot-counting";
+    meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+
+    nodes = {
+      machine = { pkgs, lib, nodes, ... }: {
+        imports = [ common ];
+        system.extraDependencies = [ nodes.other_machine.system.build.toplevel ];
+        boot.loader.systemd-boot.bootCounting.enable = withBootCounting;
+      };
+
+      other_machine = { pkgs, lib, ... }: {
+        imports = [ common ];
+        boot.loader.systemd-boot.bootCounting.enable = withBootCounting;
+        environment.systemPackages = [ pkgs.hello ];
+      };
+    };
+    testScript = { nodes, ... }:
+      let
+        orig = nodes.machine.system.build.toplevel;
+        other = nodes.other_machine.system.build.toplevel;
+      in
+      ''
+        orig = "${orig}"
+        other = "${other}"
+
+        def check_current_system(system_path):
+            machine.succeed(f'test $(readlink -f /run/current-system) = "{system_path}"')
+
+        check_current_system(orig)
+
+        # Switch to other configuration
+        machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${other}")
+        machine.succeed(f"{other}/bin/switch-to-configuration boot")
+        # Rollback, default entry is now generation 1
+        machine.succeed("nix-env -p /nix/var/nix/profiles/system --rollback")
+        machine.succeed(f"{orig}/bin/switch-to-configuration boot")
+        machine.shutdown()
+        machine.start()
+        machine.wait_for_unit("multi-user.target")
+        # Check that we booted generation 1 (default)
+        # even though generation 2 comes first in alphabetical order
+        check_current_system(orig)
+      '';
+  };
+
+
+  bootCounting =
+    let
+      baseConfig = { pkgs, lib, ... }: {
+        imports = [ common ];
+        boot.loader.systemd-boot.bootCounting.enable = true;
+        boot.loader.systemd-boot.bootCounting.tries = 2;
+      };
+    in
+    makeTest {
+      name = "systemd-boot-counting";
+      meta.maintainers = with pkgs.lib.maintainers; [ julienmalka ];
+
+      nodes = {
+        machine = { pkgs, lib, nodes, ... }: {
+          imports = [ baseConfig ];
+          system.extraDependencies = [ nodes.bad_machine.system.build.toplevel ];
+        };
+
+        bad_machine = { pkgs, lib, ... }: {
+          imports = [ baseConfig ];
+
+          systemd.services."failing" = {
+            script = "exit 1";
+            requiredBy = [ "boot-complete.target" ];
+            before = [ "boot-complete.target" ];
+            serviceConfig.Type = "oneshot";
+          };
+        };
+      };
+      testScript = { nodes, ... }:
+        let
+          orig = nodes.machine.system.build.toplevel;
+          bad = nodes.bad_machine.system.build.toplevel;
+        in
+        ''
+          orig = "${orig}"
+          bad = "${bad}"
+
+          def check_current_system(system_path):
+              machine.succeed(f'test $(readlink -f /run/current-system) = "{system_path}"')
+
+          # Ensure we booted using an entry with counters enabled
+          machine.succeed(
+              "test -e /sys/firmware/efi/efivars/LoaderBootCountPath-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
+          )
+
+          # systemd-bless-boot should have already removed the "+2" suffix from the boot entry
+          machine.wait_for_unit("systemd-bless-boot.service")
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+          check_current_system(orig)
+
+          # Switch to bad configuration
+          machine.succeed("nix-env -p /nix/var/nix/profiles/system --set ${bad}")
+          machine.succeed(f"{bad}/bin/switch-to-configuration boot")
+
+          # Ensure new bootloader entry has initialized counter
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-2+2.conf")
+          machine.shutdown()
+
+          machine.start()
+          machine.wait_for_unit("multi-user.target")
+          check_current_system(bad)
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-2+1-1.conf")
+          machine.shutdown()
+
+          machine.start()
+          machine.wait_for_unit("multi-user.target")
+          check_current_system(bad)
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-2+0-2.conf")
+          machine.shutdown()
+
+          # Should boot back into original configuration
+          machine.start()
+          check_current_system(orig)
+          machine.wait_for_unit("multi-user.target")
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-1.conf")
+          machine.succeed("test -e /boot/loader/entries/nixos-generation-2+0-2.conf")
+          machine.shutdown()
+        '';
+    };
+  defaultEntryWithBootCounting = defaultEntry { withBootCounting = true; };
+  garbageCollectEntryWithBootCounting = garbage-collect-entry { withBootCounting = true; };
 }
diff --git a/nixos/tests/systemd-homed.nix b/nixos/tests/systemd-homed.nix
index ecc92e98eddc7..5e723f6769452 100644
--- a/nixos/tests/systemd-homed.nix
+++ b/nixos/tests/systemd-homed.nix
@@ -1,7 +1,7 @@
 import ./make-test-python.nix ({ pkgs, lib, ... }:
 let
-  password = "foobar";
-  newPass = "barfoo";
+  password = "foobarfoo";
+  newPass = "barfoobar";
 in
 {
   name = "systemd-homed";
diff --git a/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix b/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix
index fda9f35cbe104..8c0ebeee97c77 100644
--- a/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix
+++ b/nixos/tests/systemd-networkd-dhcpserver-static-leases.nix
@@ -24,8 +24,6 @@ import ./make-test-python.nix ({ lib, ... }: {
           "01-eth1" = {
             name = "eth1";
             networkConfig = {
-              # IPForward prevents dynamic address configuration
-              IPForward = true;
               DHCPServer = true;
               Address = "10.0.0.1/24";
             };
diff --git a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
index 6c056d9a10183..2ea6d0effd536 100644
--- a/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
+++ b/nixos/tests/systemd-networkd-ipv6-prefix-delegation.nix
@@ -40,7 +40,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
             address = [
               "2001:DB8::1/64"
             ];
-            networkConfig.IPForward = true;
+            networkConfig.IPv4Forwarding = true;
+            networkConfig.IPv6Forwarding = true;
           };
         };
       };
@@ -174,6 +175,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
         # for fowarding/input from the configured interfaces so you do not have
         # to manage multiple places
         firewall.enable = false;
+        interfaces.eth1.ipv6.addresses = lib.mkForce [ ];
       };
 
       systemd.network = {
@@ -275,6 +277,7 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
       networking = {
         useNetworkd = true;
         useDHCP = false;
+        interfaces.eth1.ipv6.addresses = lib.mkForce [ ];
       };
     };
   };
diff --git a/nixos/tests/systemd-networkd-vrf.nix b/nixos/tests/systemd-networkd-vrf.nix
index 4f2a45577c169..a7875bb177faf 100644
--- a/nixos/tests/systemd-networkd-vrf.nix
+++ b/nixos/tests/systemd-networkd-vrf.nix
@@ -16,7 +16,8 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: let
         linkConfig.RequiredForOnline = "no";
         networkConfig = {
           Address = "192.168.${toString vlan}.${toString id}/24";
-          IPForward = "yes";
+          IPv4Forwarding = "yes";
+          IPv6Forwarding = "yes";
         };
       };
     };
@@ -57,14 +58,16 @@ in {
 
         networks."10-vrf1" = {
           matchConfig.Name = "vrf1";
-          networkConfig.IPForward = "yes";
+          networkConfig.IPv4Forwarding = "yes";
+          networkConfig.IPv6Forwarding = "yes";
           routes = [
             { Destination = "192.168.1.2"; Metric = 100; }
           ];
         };
         networks."10-vrf2" = {
           matchConfig.Name = "vrf2";
-          networkConfig.IPForward = "yes";
+          networkConfig.IPv4Forwarding = "yes";
+          networkConfig.IPv6Forwarding = "yes";
           routes = [
             { Destination = "192.168.2.3"; Metric = 100; }
           ];
@@ -76,7 +79,8 @@ in {
           networkConfig = {
             VRF = "vrf1";
             Address = "192.168.1.1/24";
-            IPForward = "yes";
+            IPv4Forwarding = "yes";
+            IPv6Forwarding = "yes";
           };
         };
         networks."10-eth2" = {
@@ -85,7 +89,8 @@ in {
           networkConfig = {
             VRF = "vrf2";
             Address = "192.168.2.1/24";
-            IPForward = "yes";
+            IPv4Forwarding = "yes";
+            IPv6Forwarding = "yes";
           };
         };
       };
diff --git a/nixos/tests/systemd-networkd.nix b/nixos/tests/systemd-networkd.nix
index 44ad713cd6dfc..a595fb9cba4ac 100644
--- a/nixos/tests/systemd-networkd.nix
+++ b/nixos/tests/systemd-networkd.nix
@@ -57,6 +57,8 @@ let generateNodeConf = { lib, pkgs, config, privk, pubk, peerId, nodeId, ...}: {
               { Table = 30; From = "192.168.1.1"; To = "192.168.1.2"; SourcePort = 666 ; DestinationPort = 667; }
               { Table = 40; IPProtocol = "tcp"; InvertRule = true; }
               { Table = 50; IncomingInterface = "eth1"; Family = "ipv4"; }
+              { Table = 60; FirewallMark = 4; }
+              { Table = 70; FirewallMark = "16/0x1f"; }
             ];
           };
         };
@@ -119,5 +121,9 @@ testScript = ''
     )
     # IPProtocol + InvertRule
     node1.succeed("sudo ip rule | grep 'not from all ipproto tcp lookup 40'")
+    # FirewallMark without a mask
+    node1.succeed("sudo ip rule | grep 'from all fwmark 0x4 lookup 60'")
+    # FirewallMark with a mask
+    node1.succeed("sudo ip rule | grep 'from all fwmark 0x10/0x1f lookup 70'")
 '';
 })
diff --git a/nixos/tests/systemd-sysusers-immutable.nix b/nixos/tests/systemd-sysusers-immutable.nix
index 42cbf84d175e4..4d65b52a0d336 100644
--- a/nixos/tests/systemd-sysusers-immutable.nix
+++ b/nixos/tests/systemd-sysusers-immutable.nix
@@ -2,8 +2,8 @@
 
 let
   rootPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
-  normaloPassword = "$y$j9T$3aiOV/8CADAK22OK2QT3/0$67OKd50Z4qTaZ8c/eRWHLIM.o3ujtC1.n9ysmJfv639";
-  newNormaloPassword = "mellow";
+  sysuserPassword = "$y$j9T$3aiOV/8CADAK22OK2QT3/0$67OKd50Z4qTaZ8c/eRWHLIM.o3ujtC1.n9ysmJfv639";
+  newSysuserPassword = "mellow";
 in
 
 {
@@ -16,49 +16,48 @@ in
     systemd.sysusers.enable = true;
     users.mutableUsers = false;
 
-    # Override the empty root password set by the test instrumentation
-    users.users.root.hashedPasswordFile = lib.mkForce null;
-    users.users.root.initialHashedPassword = rootPassword;
-    users.users.normalo = {
-      isNormalUser = true;
-      initialHashedPassword = normaloPassword;
+
+    # Read this password file at runtime from outside the Nix store.
+    environment.etc."rootpw.secret".text = rootPassword;
+    # Override the empty root password set by the test instrumentation.
+    users.users.root.hashedPasswordFile = lib.mkForce "/etc/rootpw.secret";
+
+    users.users.sysuser = {
+      isSystemUser = true;
+      group = "wheel";
+      home = "/sysuser";
+      initialHashedPassword = sysuserPassword;
     };
 
     specialisation.new-generation.configuration = {
-      users.users.new-normalo = {
-        isNormalUser = true;
-        initialPassword = newNormaloPassword;
+      users.users.new-sysuser = {
+        isSystemUser = true;
+        group = "wheel";
+        home = "/new-sysuser";
+        initialPassword = newSysuserPassword;
       };
     };
   };
 
   testScript = ''
-    with subtest("Users are not created with systemd-sysusers"):
-      machine.fail("systemctl status systemd-sysusers.service")
-      machine.fail("ls /etc/sysusers.d")
-
-    with subtest("Correct mode on the password files"):
-      assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
-      assert machine.succeed("stat -c '%a' /etc/group") == "644\n"
-      assert machine.succeed("stat -c '%a' /etc/shadow") == "0\n"
-      assert machine.succeed("stat -c '%a' /etc/gshadow") == "0\n"
-
     with subtest("root user has correct password"):
       print(machine.succeed("getent passwd root"))
       assert "${rootPassword}" in machine.succeed("getent shadow root"), "root user password is not correct"
 
-    with subtest("normalo user is created"):
-      print(machine.succeed("getent passwd normalo"))
-      assert machine.succeed("stat -c '%U' /home/normalo") == "normalo\n"
-      assert "${normaloPassword}" in machine.succeed("getent shadow normalo"), "normalo user password is not correct"
+    with subtest("sysuser user is created"):
+      print(machine.succeed("getent passwd sysuser"))
+      assert machine.succeed("stat -c '%U' /sysuser") == "sysuser\n"
+      assert "${sysuserPassword}" in machine.succeed("getent shadow sysuser"), "sysuser user password is not correct"
+
+    with subtest("Fail to add new user manually"):
+      machine.fail("useradd manual-sysuser")
 
 
     machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
 
 
-    with subtest("new-normalo user is created after switching to new generation"):
-      print(machine.succeed("getent passwd new-normalo"))
-      print(machine.succeed("getent shadow new-normalo"))
-      assert machine.succeed("stat -c '%U' /home/new-normalo") == "new-normalo\n"
+    with subtest("new-sysuser user is created after switching to new generation"):
+      print(machine.succeed("getent passwd new-sysuser"))
+      assert machine.succeed("stat -c '%U' /new-sysuser") == "new-sysuser\n"
   '';
 }
diff --git a/nixos/tests/systemd-sysusers-mutable.nix b/nixos/tests/systemd-sysusers-mutable.nix
index e69cfe23a59a1..9871a91cca971 100644
--- a/nixos/tests/systemd-sysusers-mutable.nix
+++ b/nixos/tests/systemd-sysusers-mutable.nix
@@ -2,8 +2,8 @@
 
 let
   rootPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
-  normaloPassword = "hello";
-  newNormaloPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
+  sysuserPassword = "hello";
+  newSysuserPassword = "$y$j9T$p6OI0WN7.rSfZBOijjRdR.$xUOA2MTcB48ac.9Oc5fz8cxwLv1mMqabnn333iOzSA6";
 in
 
 {
@@ -24,15 +24,19 @@ in
     # Override the empty root password set by the test instrumentation
     users.users.root.hashedPasswordFile = lib.mkForce null;
     users.users.root.initialHashedPassword = rootPassword;
-    users.users.normalo = {
-      isNormalUser = true;
-      initialPassword = normaloPassword;
+    users.users.sysuser = {
+      isSystemUser = true;
+      group = "wheel";
+      home = "/sysuser";
+      initialPassword = sysuserPassword;
     };
 
     specialisation.new-generation.configuration = {
-      users.users.new-normalo = {
-        isNormalUser = true;
-        initialHashedPassword = newNormaloPassword;
+      users.users.new-sysuser = {
+        isSystemUser = true;
+        group = "wheel";
+        home = "/new-sysuser";
+        initialHashedPassword = newSysuserPassword;
       };
     };
   };
@@ -43,7 +47,7 @@ in
     with subtest("systemd-sysusers.service contains the credentials"):
       sysusers_service = machine.succeed("systemctl cat systemd-sysusers.service")
       print(sysusers_service)
-      assert "SetCredential=passwd.plaintext-password.normalo:${normaloPassword}" in sysusers_service
+      assert "SetCredential=passwd.plaintext-password.sysuser:${sysuserPassword}" in sysusers_service
 
     with subtest("Correct mode on the password files"):
       assert machine.succeed("stat -c '%a' /etc/passwd") == "644\n"
@@ -55,17 +59,20 @@ in
       print(machine.succeed("getent passwd root"))
       assert "${rootPassword}" in machine.succeed("getent shadow root"), "root user password is not correct"
 
-    with subtest("normalo user is created"):
-      print(machine.succeed("getent passwd normalo"))
-      assert machine.succeed("stat -c '%U' /home/normalo") == "normalo\n"
+    with subtest("sysuser user is created"):
+      print(machine.succeed("getent passwd sysuser"))
+      assert machine.succeed("stat -c '%U' /sysuser") == "sysuser\n"
+
+    with subtest("Manually add new user"):
+      machine.succeed("useradd manual-sysuser")
 
 
     machine.succeed("/run/current-system/specialisation/new-generation/bin/switch-to-configuration switch")
 
 
-    with subtest("new-normalo user is created after switching to new generation"):
-      print(machine.succeed("getent passwd new-normalo"))
-      assert machine.succeed("stat -c '%U' /home/new-normalo") == "new-normalo\n"
-      assert "${newNormaloPassword}" in machine.succeed("getent shadow new-normalo"), "new-normalo user password is not correct"
+    with subtest("new-sysuser user is created after switching to new generation"):
+      print(machine.succeed("getent passwd new-sysuser"))
+      assert machine.succeed("stat -c '%U' /new-sysuser") == "new-sysuser\n"
+      assert "${newSysuserPassword}" in machine.succeed("getent shadow new-sysuser"), "new-sysuser user password is not correct"
   '';
 }
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index 4b087d403f37d..3430eb9398cb4 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -204,8 +204,7 @@ import ./make-test-python.nix ({ pkgs, ... }: {
         assert "0B read, 0B written" not in output
 
     with subtest("systemd per-unit accounting works"):
-        assert "IP traffic received: 84B" in output_ping
-        assert "IP traffic sent: 84B" in output_ping
+        assert "IP traffic received: 84B sent: 84B" in output_ping
 
     with subtest("systemd environment is properly set"):
         machine.systemctl("daemon-reexec")  # Rewrites /proc/1/environ
diff --git a/nixos/tests/teleport.nix b/nixos/tests/teleport.nix
index 3621cce0599e1..0d0b9a713065a 100644
--- a/nixos/tests/teleport.nix
+++ b/nixos/tests/teleport.nix
@@ -10,6 +10,7 @@ let
   packages = with pkgs; {
     "default" = teleport;
     "14" = teleport_14;
+    "15" = teleport_15;
   };
 
   minimal = package: {
diff --git a/nixos/tests/teleports.nix b/nixos/tests/teleports.nix
new file mode 100644
index 0000000000000..a4293f954a455
--- /dev/null
+++ b/nixos/tests/teleports.nix
@@ -0,0 +1,48 @@
+{ pkgs, lib, ... }:
+{
+  name = "teleports-standalone";
+  meta.maintainers = lib.teams.lomiri.members;
+
+  nodes.machine =
+    { config, pkgs, ... }:
+    {
+      imports = [ ./common/x11.nix ];
+
+      services.xserver.enable = true;
+
+      environment = {
+        systemPackages = with pkgs.lomiri; [
+          suru-icon-theme
+          teleports
+        ];
+        variables = {
+          UITK_ICON_THEME = "suru";
+        };
+      };
+
+      i18n.supportedLocales = [ "all" ];
+
+      fonts.packages = with pkgs; [
+        # Intended font & helps with OCR
+        ubuntu-classic
+      ];
+    };
+
+  enableOCR = true;
+
+  testScript = ''
+    machine.wait_for_x()
+
+    with subtest("teleports launches"):
+        machine.execute("teleports >&2 &")
+        machine.wait_for_text(r"(TELEports|Phone Number)")
+        machine.screenshot("teleports_open")
+
+    machine.succeed("pkill -f teleports")
+
+    with subtest("teleports localisation works"):
+        machine.execute("env LANG=de_DE.UTF-8 teleports >&2 &")
+        machine.wait_for_text("Telefonnummer")
+        machine.screenshot("teleports_localised")
+  '';
+}
diff --git a/nixos/tests/tika.nix b/nixos/tests/tika.nix
new file mode 100644
index 0000000000000..61a3a6ad22aed
--- /dev/null
+++ b/nixos/tests/tika.nix
@@ -0,0 +1,21 @@
+{ lib, ... }:
+
+{
+  name = "tika-server";
+
+  nodes = {
+    machine = { pkgs, ... }: {
+      services.tika = {
+        enable = true;
+      };
+    };
+  };
+
+  testScript = ''
+    machine.start()
+    machine.wait_for_unit("tika.service")
+    machine.wait_for_open_port(9998)
+  '';
+
+  meta.maintainers = [ lib.maintainers.drupol ];
+}
diff --git a/nixos/tests/user-home-mode.nix b/nixos/tests/user-home-mode.nix
index 070cb0b75cc9d..2d6d1af3f391b 100644
--- a/nixos/tests/user-home-mode.nix
+++ b/nixos/tests/user-home-mode.nix
@@ -12,6 +12,12 @@ import ./make-test-python.nix ({ lib, ... }: {
       isNormalUser = true;
       homeMode = "750";
     };
+    users.users.carol = {
+      initialPassword = "pass3";
+      isNormalUser = true;
+      createHome = true;
+      home = "/users/carol";
+    };
   };
 
   testScript = ''
@@ -23,5 +29,7 @@ import ./make-test-python.nix ({ lib, ... }: {
     machine.send_chars("pass1\n")
     machine.succeed('[ "$(stat -c %a /home/alice)" == "700" ]')
     machine.succeed('[ "$(stat -c %a /home/bob)" == "750" ]')
+    machine.succeed('[ "$(stat -c %a /users)" == "755" ]')
+    machine.succeed('[ "$(stat -c %a /users/carol)" == "700" ]')
   '';
 })
diff --git a/nixos/tests/vaultwarden.nix b/nixos/tests/vaultwarden.nix
index a60cb3af5535c..b51a147be99d3 100644
--- a/nixos/tests/vaultwarden.nix
+++ b/nixos/tests/vaultwarden.nix
@@ -122,7 +122,7 @@ let
           };
 
           sqlite = {
-            services.vaultwarden.backupDir = "/var/lib/vaultwarden/backups";
+            services.vaultwarden.backupDir = "/srv/backups/vaultwarden";
 
             environment.systemPackages = [ pkgs.sqlite ];
           };
@@ -133,7 +133,7 @@ let
             enable = true;
             dbBackend = backend;
             config = {
-              rocketAddress = "0.0.0.0";
+              rocketAddress = "::";
               rocketPort = 8080;
             };
           };
@@ -205,13 +205,12 @@ builtins.mapAttrs (k: v: makeVaultwardenTest k v) {
           server.start_job("backup-vaultwarden.service")
 
       with subtest("Check that backup exists"):
-          server.succeed('[ -d "/var/lib/vaultwarden/backups" ]')
-          server.succeed('[ -f "/var/lib/vaultwarden/backups/db.sqlite3" ]')
-          server.succeed('[ -d "/var/lib/vaultwarden/backups/attachments" ]')
-          server.succeed('[ -f "/var/lib/vaultwarden/backups/rsa_key.pem" ]')
-          server.succeed('[ -f "/var/lib/vaultwarden/backups/rsa_key.pub.pem" ]')
+          server.succeed('[ -d "/srv/backups/vaultwarden" ]')
+          server.succeed('[ -f "/srv/backups/vaultwarden/db.sqlite3" ]')
+          server.succeed('[ -d "/srv/backups/vaultwarden/attachments" ]')
+          server.succeed('[ -f "/srv/backups/vaultwarden/rsa_key.pem" ]')
           # Ensure only the db backed up with the backup command exists and not the other db files.
-          server.succeed('[ ! -f "/var/lib/vaultwarden/backups/db.sqlite3-shm" ]')
+          server.succeed('[ ! -f "/srv/backups/vaultwarden/db.sqlite3-shm" ]')
     '';
   };
 }
diff --git a/nixos/tests/vector/dnstap.nix b/nixos/tests/vector/dnstap.nix
index 15d643311b604..5143fd938fdef 100644
--- a/nixos/tests/vector/dnstap.nix
+++ b/nixos/tests/vector/dnstap.nix
@@ -49,7 +49,7 @@ in
         settings = {
           server = {
             interface = [ "0.0.0.0" "::" ];
-            access-control = [ "192.168.1.0/24 allow" ];
+            access-control = [ "192.168.0.0/24 allow" "::/0 allow" ];
 
             domain-insecure = "local";
             private-domain = "local";
diff --git a/nixos/tests/vector/syslog-quickwit.nix b/nixos/tests/vector/syslog-quickwit.nix
index 89c46d42ee75c..cb6e04e00eae4 100644
--- a/nixos/tests/vector/syslog-quickwit.nix
+++ b/nixos/tests/vector/syslog-quickwit.nix
@@ -61,6 +61,7 @@ import ../make-test-python.nix ({ lib, pkgs, ... }:
                 }
                 .scope_name = structured.msgid
                 del(.message)
+                del(.host)
                 del(.timestamp)
                 del(.service)
                 del(.source_type)
diff --git a/nixos/tests/vscode-remote-ssh.nix b/nixos/tests/vscode-remote-ssh.nix
index de7cc6badc9a2..2b2630ef87d2f 100644
--- a/nixos/tests/vscode-remote-ssh.nix
+++ b/nixos/tests/vscode-remote-ssh.nix
@@ -14,7 +14,7 @@ import ./make-test-python.nix ({ lib, ... }@args: let
   inherit (pkgs.vscode.passthru) rev vscodeServer;
 in {
   name = "vscode-remote-ssh";
-  meta.maintainers = with lib.maintainers; [ Enzime ];
+  meta.maintainers = [ ];
 
   nodes = let
     serverAddress = "192.168.0.2";
diff --git a/nixos/tests/web-apps/mastodon/default.nix b/nixos/tests/web-apps/mastodon/default.nix
index 178590d13b63c..7f925b9ad4ed2 100644
--- a/nixos/tests/web-apps/mastodon/default.nix
+++ b/nixos/tests/web-apps/mastodon/default.nix
@@ -1,9 +1,9 @@
-{ system ? builtins.currentSystem, handleTestOn }:
+{ system ? builtins.currentSystem, pkgs, handleTestOn, ... }:
 let
   supportedSystems = [ "x86_64-linux" "i686-linux" "aarch64-linux" ];
 
 in
 {
-  standard = handleTestOn supportedSystems ./standard.nix { inherit system; };
-  remote-databases = handleTestOn supportedSystems ./remote-databases.nix { inherit system; };
+  standard = handleTestOn supportedSystems ./standard.nix { inherit system pkgs; };
+  remote-databases = handleTestOn supportedSystems ./remote-databases.nix { inherit system pkgs; };
 }
diff --git a/nixos/tests/web-apps/pixelfed/standard.nix b/nixos/tests/web-apps/pixelfed/standard.nix
index 9260e27af960d..c575ee0b0f76c 100644
--- a/nixos/tests/web-apps/pixelfed/standard.nix
+++ b/nixos/tests/web-apps/pixelfed/standard.nix
@@ -1,7 +1,6 @@
-import ../../make-test-python.nix ({pkgs, ...}:
-{
+import ../../make-test-python.nix {
   name = "pixelfed-standard";
-  meta.maintainers = with pkgs.lib.maintainers; [ raitobezarius ];
+  meta.maintainers = [ ];
 
   nodes = {
     server = { pkgs, ... }: {
@@ -35,4 +34,4 @@ import ../../make-test-python.nix ({pkgs, ...}:
     # 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/pretix.nix b/nixos/tests/web-apps/pretix.nix
index 559316f9b85cb..ac89a7b3fec30 100644
--- a/nixos/tests/web-apps/pretix.nix
+++ b/nixos/tests/web-apps/pretix.nix
@@ -20,6 +20,7 @@
         plugins = with pkgs.pretix.plugins; [
           passbook
           pages
+          zugferd
         ];
         settings = {
           pretix = {
diff --git a/nixos/tests/web-apps/weblate.nix b/nixos/tests/web-apps/weblate.nix
new file mode 100644
index 0000000000000..40d60f7e5f996
--- /dev/null
+++ b/nixos/tests/web-apps/weblate.nix
@@ -0,0 +1,104 @@
+import ../make-test-python.nix (
+  { pkgs, ... }:
+
+  let
+    certs = import ../common/acme/server/snakeoil-certs.nix;
+
+    serverDomain = certs.domain;
+
+    admin = {
+      username = "admin";
+      password = "snakeoilpass";
+    };
+    # An API token that we manually insert into the db as a valid one.
+    apiToken = "OVJh65sXaAfQMZ4NTcIGbFZIyBZbEZqWTi7azdDf";
+  in
+  {
+    name = "weblate";
+    meta.maintainers = with pkgs.lib.maintainers; [ erictapen ];
+
+    nodes.server =
+      { pkgs, lib, ... }:
+      {
+        virtualisation.memorySize = 2048;
+
+        services.weblate = {
+          enable = true;
+          localDomain = "${serverDomain}";
+          djangoSecretKeyFile = pkgs.writeText "weblate-django-secret" "thisissnakeoilsecretwithmorethan50characterscorrecthorsebatterystaple";
+          extraConfig = ''
+            # Weblate tries to fetch Avatars from the network
+            ENABLE_AVATARS = False
+          '';
+        };
+
+        services.nginx.virtualHosts."${serverDomain}" = {
+          enableACME = lib.mkForce false;
+          sslCertificate = certs."${serverDomain}".cert;
+          sslCertificateKey = certs."${serverDomain}".key;
+        };
+
+        security.pki.certificateFiles = [ certs.ca.cert ];
+
+        networking.hosts."::1" = [ "${serverDomain}" ];
+        networking.firewall.allowedTCPPorts = [
+          80
+          443
+        ];
+
+        users.users.weblate.shell = pkgs.bashInteractive;
+      };
+
+    nodes.client =
+      { pkgs, nodes, ... }:
+      {
+        environment.systemPackages = [ pkgs.wlc ];
+
+        environment.etc."xdg/weblate".text = ''
+          [weblate]
+          url = https://${serverDomain}/api/
+          key = ${apiToken}
+        '';
+
+        networking.hosts."${nodes.server.networking.primaryIPAddress}" = [ "${serverDomain}" ];
+
+        security.pki.certificateFiles = [ certs.ca.cert ];
+      };
+
+    testScript = ''
+      import json
+
+      start_all()
+      server.wait_for_unit("weblate.socket")
+      server.wait_until_succeeds("curl -f https://${serverDomain}/")
+      server.succeed("sudo -iu weblate -- weblate createadmin --username ${admin.username} --password ${admin.password} --email weblate@example.org")
+
+      # It's easier to replace the generated API token with a predefined one than
+      # to extract it at runtime.
+      server.succeed("sudo -iu weblate -- psql -d weblate -c \"UPDATE authtoken_token SET key = '${apiToken}' WHERE user_id = (SELECT id FROM weblate_auth_user WHERE username = 'admin');\"")
+
+      client.wait_for_unit("multi-user.target")
+
+      # Test the official Weblate client wlc.
+      client.wait_until_succeeds("REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt wlc --debug list-projects")
+
+      def call_wl_api(arg):
+          (rv, result) = client.execute("curl -H \"Content-Type: application/json\" -H \"Authorization: Token ${apiToken}\" https://${serverDomain}/api/{}".format(arg))
+          assert rv == 0
+          print(result)
+
+      call_wl_api("users/ --data '{}'".format(
+        json.dumps(
+          {"username": "test1",
+            "full_name": "test1",
+            "email": "test1@example.org"
+          })))
+
+      # TODO: Check sending and receiving email.
+      # server.wait_for_unit("postfix.service")
+
+      # TODO: The goal is for this to succeed, but there are still some checks failing.
+      # server.succeed("sudo -iu weblate -- weblate check --deploy")
+    '';
+  }
+)